Filsuf uga-panganan utawa program competitive ing .NET
Ayo kang katon ing carane program bebarengan lan podo dianggo ing .Net, nggunakake conto masalah filsuf lunching. Rencana kasebut kaya ing ngisor iki, saka sinkronisasi benang / proses menyang model aktor (ing bagean ing ngisor iki). Artikel kasebut bisa migunani kanggo kenalan pisanan utawa kanggo nyegerake kawruh.
Apa malah ngerti carane nindakake iki? Transistor wis tekan ukuran minimal, hukum Moore wis tekan wates kacepetan cahya, lan mulane wutah diamati ing nomer; transistor liyane bisa digawe. Ing wektu sing padha, jumlah data saya akeh, lan pangguna ngarepake respon langsung saka sistem. Ing kahanan kaya mengkono, pemrograman "normal", nalika kita duwe thread eksekusi, ora efektif maneh. Kita kudu ngatasi masalah eksekusi simultan utawa bebarengan. Menapa malih, masalah iki ana ing tingkat sing beda-beda: ing tingkat thread, ing tingkat proses, ing tingkat mesin ing jaringan (sistem sing disebarake). .NET nduweni kualitas dhuwur, teknologi sing diuji wektu kanggo ngrampungake masalah kasebut kanthi cepet lan efisien.
Tujuan
Edsger Dijkstra takon masalah iki marang murid-muride nalika taun 1965. Rumusan kang wis ditetepake kaya ing ngisor iki. Ana sawetara filsuf tartamtu (biasane lima) lan jumlah garpu sing padha. Padha njagong ing meja bunder, garpu ing antarane. Para filsuf bisa mangan saka piring panganan tanpa telas, mikir utawa ngenteni. Kanggo mangan, filsuf kudu njupuk rong garpu (sing terakhir nuduhake garpu karo mantan). Njupuk lan nyelehake garpu minangka rong tumindak sing kapisah. Kabeh filsuf meneng. Tugase yaiku nemokake algoritma kasebut supaya kabeh padha mikir lan kepenak sanajan sawise 54 taun.
Pisanan, ayo nyoba ngatasi masalah iki kanthi nggunakake papan sing dienggo bareng. Garpu kasebut ana ing meja umum lan para filsuf mung njupuk nalika ana lan dilebokake maneh. Iki ngendi masalah sinkronisasi muncul, nalika persis kanggo njupuk garpu? apa sing kudu ditindakake yen ora ana plug? lsp Nanging pisanan, ayo miwiti karo para filsuf.
Kanggo miwiti Utas kita nggunakake blumbang Utas liwat Task.Run cara:
var cancelTokenSource = new CancellationTokenSource();
Action<int> create = (i) => RunPhilosopher(i, cancelTokenSource.Token);
for (int i = 0; i < philosophersAmount; i++)
{
int icopy = i;
// ΠΠΎΠΌΠ΅ΡΡΠΈΡΡ Π·Π°Π΄Π°ΡΡ Π² ΠΎΡΠ΅ΡΠ΅Π΄Ρ ΠΏΡΠ»Π° ΠΏΠΎΡΠΎΠΊΠΎΠ². ΠΠ΅ΡΠΎΠ΄ RunDeadlock Π½Π΅ Π·Π°ΠΏΡΡΠΊΠ°Π΅ΡΡΡΡ
// ΡΡΠ°Π·Ρ, Π° ΠΆΠ΄Π΅Ρ ΡΠ²ΠΎΠ΅Π³ΠΎ ΠΏΠΎΡΠΎΠΊΠ°. ΠΡΠΈΠ½Ρ ΡΠΎΠ½Π½ΡΠΉ Π·Π°ΠΏΡΡΠΊ.
philosophers[i] = Task.Run(() => create(icopy), cancelTokenSource.Token);
}
Kolam benang dirancang kanggo ngoptimalake nggawe lan mbusak benang. Kolam iki duwe antrian tugas lan CLR nggawe utawa mbusak benang gumantung saka jumlah tugas kasebut. Siji blumbang kanggo kabeh AppDomains. Kolam iki kudu digunakake meh tansah, amarga ... ora perlu repot nggawe lan mbusak utas, antrian, lsp. Sampeyan bisa nindakake tanpa blumbang, nanging sampeyan kudu nggunakake langsung. Thread, iki migunani kanggo kasus nalika kita kudu ngganti prioritas thread, nalika kita duwe operasi dawa, kanggo thread Foreground, etc.
Ing tembung liyane, System.Threading.Tasks.Task kelas padha Thread, nanging kanthi macem-macem kepenak: kemampuan kanggo mbukak tugas sawise blok tugas liyane, bali saka fungsi, gampang ngganggu, lan liya-liyane. etc.. Padha dibutuhake kanggo ndhukung konstruksi async / ngenteni (Pola Asynchronous basis Tugas, gula sintaksis kanggo nunggu operasi IO). Kita bakal ngomong babagan iki mengko.
CancelationTokenSource kene iku perlu sing Utas bisa siksa dhewe marang sinyal saka Utas nelpon.
Masalah Sinkronisasi
Filsuf sing diblokir
Oke, kita ngerti carane nggawe utas, ayo nyoba nedha awan:
Ing kene kita nyoba njupuk sisih kiwa lan banjur garpu tengen, lan yen bisa, kita mangan lan sijine maneh. Njupuk garpu siji iku atom, i.e. loro Utas ora bisa njupuk siji ing wektu sing padha (salah: pisanan maca sing garpu free, kaloro nindakake padha, pisanan njupuk, kaloro njupuk). Kanggo iki Interlocked.CompareExchange, sing kudu dileksanakake nggunakake instruksi prosesor (TSL, XCHG), sing ngunci sepotong memori kanggo maca lan nulis urutan atom. Lan SpinWait padha karo construction while(true) mung karo "sihir" sethitik - thread njupuk prosesor (Thread.SpinWait), nanging kadhangkala liwat kontrol kanggo thread liyane (Thread.Yeild) utawa turu (Thread.Sleep).
Nanging solusi iki ora bisa digunakake, amarga ... Utas rauh (sajrone sekedhik kanggo kula) diblokir: kabeh filsuf njupuk garpu kiwa, nanging ora ana siji tengen. Array forks banjur nduweni nilai: 1 2 3 4 5.
Ing gambar, mblokir thread (deadlock). Ijo nuduhake eksekusi, abang nuduhake sinkronisasi, lan werna abu-abu nuduhake benang turu. Diamonds nuduhake wektu Bukak saka Tugas.
Keluwen para Filsuf
Senajan sampeyan ora perlu akeh pangan kanggo mikir, keluwen bisa meksa sapa kanggo nyerah filsafat. Ayo coba simulasi kahanan keluwen benang ing masalah kita. Keluwen yaiku nalika benang bisa, nanging tanpa karya sing signifikan, kanthi tembung liya, iku buntu sing padha, mung saiki benang ora turu, nanging aktif golek panganan, nanging ora ana panganan. Supaya ora kerep diblokir, kita bakal nyelehake garpu maneh yen ora bisa njupuk liyane.
Sing penting babagan kode iki yaiku loro saka papat filsuf lali nyelehake garpu kiwa. Lan ternyata mangan luwih akeh, lan wong liya wiwit keluwen, sanajan benang duwe prioritas sing padha. Ing kene dheweke ora keluwen babar pisan, amarga ... filsuf ala kadhangkala sijine maneh garpu. Pranyata sing apik mangan kurang 5 kaping saka sing ala. Dadi kesalahan cilik ing kode nyebabake penurunan kinerja. Ing kene uga perlu dicathet menawa kahanan sing langka bisa kedadeyan nalika kabeh filsuf njupuk garpu kiwa, ora ana sing tengen, sijine sing kiwa mudhun, ngenteni, njupuk sing kiwa maneh, lsp. Kahanan iki uga keluwen, luwih kaya sumbatan bebarengan. Aku ora bisa mbaleni. Ing ngisor iki ana gambar kanggo kahanan ing ngendi loro filsuf ala wis njupuk loro garpu, lan loro apik keluwen.
Ing kene sampeyan bisa ndeleng manawa benang kadang tangi lan nyoba golek sumber. Loro saka papat intine ora nindakake apa-apa (grafik ijo ing ndhuwur).
Pati Filsuf
Nah, masalah liyane sing bisa ngganggu nedha bengi para filsuf sing mulya yaiku yen salah sijine tiba-tiba mati kanthi garpu ing tangane (lan dheweke bakal dikubur kaya ngono). Banjur tangga-tanggane bakal ditinggal tanpa nedha awan. Sampeyan bisa teka munggah karo conto kode kanggo kasus iki dhewe, contone dibuwang adoh NullReferenceException sawise filsuf njupuk garpu. Lan, kanthi cara iki, pangecualian ora bakal ditangani lan kode panggilan ora mung bisa nyekel (kanggo iki AppDomain.CurrentDomain.UnhandledException lan lsp). Mulane, panangan kesalahan dibutuhake ing benang dhewe lan kanthi mandap anggun.
Pelayan
Oke, kepiye carane ngatasi masalah buntu, keluwen, lan pati? Kita bakal ngidini mung siji filsuf menyang garpu, lan kita bakal nambah bebarengan exclusion saka Utas kanggo panggonan iki. Carane nindakake? Upamane ing jejere para filsuf ana pelayan sing menehi ijin marang salah sawijining filsuf kanggo njupuk garpu. Kepiye carane nggawe pelayan iki lan kepiye para filsuf bakal takon dheweke minangka pitakonan sing menarik.
Cara sing paling gampang yaiku para filsuf mung terus-terusan takon marang pelayan kanggo ngakses garpu. Sing. Saiki filsuf ora bakal ngenteni garpu ing cedhak, nanging ngenteni utawa takon pelayan. Ing wiwitan, kita mung nggunakake Ruang Pangguna kanggo iki; ing kono kita ora nggunakake interupsi kanggo nelpon prosedur apa wae saka kernel (liyane ing ngisor iki).
Solusi ruang pangguna
Kene kita bakal nindakake bab sing padha sadurunge karo siji garpu lan loro filsuf, kita bakal muter ing daur ulang lan ngenteni. Nanging saiki bakal kabeh filsuf lan, kaya, mung siji garpu, i.e. kita bisa ngomong sing mung filsuf sing njupuk iki "garpu emas" saka waiter bakal mangan. Kanggo nindakake iki kita nggunakake SpinLock.
SpinLock iki blocker, karo, kira-kira ngandika, padha while(true) { if (!lock) break; }, nanging karo malah luwih "sihir" saka ing SpinWait (sing digunakake ing kono). Saiki dheweke ngerti carane ngetung sing nunggu, turu sethithik, lan liya-liyane. etc Umum, iku kabeh bisa kanggo ngoptimalake. Nanging kita kudu ngelingi sing iki isih daur ulang aktif padha mangan sumber daya prosesor lan terus thread, kang bisa mimpin kanggo keluwen yen salah siji saka filsuf dadi prioritas luwih saka liyane, nanging ora duwe garpu emas (Priority Inversion problem). ). Mulane, kita nggunakake mung kanggo owah-owahan cendhak banget ing memori sambungan, tanpa telpon pihak katelu, kunci nested, utawa surprises liyane.
Nggambar kanggo SpinLock. Aliran terus-terusan "perang" kanggo garpu emas. Gagal kedadeyan - wilayah sing disorot ing gambar kasebut. Intine ora digunakake kanthi lengkap: mung udakara 2/3 saka papat benang kasebut.
Solusi liyane ing kene yaiku mung nggunakake Interlocked.CompareExchange karo Enteni aktif padha minangka ditampilake ing kode ndhuwur (ing filsuf kaliren), nanging iki, minangka wis ngandika, teori bisa mimpin kanggo mblokir.
ing Interlocked iku worth ngomong sing ana ora mung CompareExchange, nanging uga cara liya kanggo maca lan nulis atom. Lan kanthi mbaleni owah-owahan kasebut, yen thread liyane bisa nggawe owah-owahan (maca 1, maca 2, nulis 2, nulis 1 ala), bisa digunakake kanggo owah-owahan kompleks kanggo siji nilai (pola Interlocked Anything).
Solusi mode kernel
Supaya ora mbuang sumber daya ing loop, ayo goleki carane mblokir thread. Ing tembung liyane, terus conto kita, ayo kang ndeleng carane waiter nempatno filsuf turu lan mung tangi yen perlu. Pisanan, ayo goleki carane nindakake iki liwat mode kernel sistem operasi. Kabeh struktur ing kono asring dadi luwih alon tinimbang ing ruang pangguna. Alon kaping pirang-pirang, contone AutoResetEvent bisa 53 kaping luwih alon SpinLock [Richter]. Nanging kanthi bantuan, sampeyan bisa nyinkronake proses ing kabeh sistem, dikelola utawa ora.
Desain dhasar ing kene yaiku semafor, sing diusulake dening Dijkstra luwih saka setengah abad kepungkur. A semaphore, mung sijine, integer positif kontrol dening sistem, lan loro operasi ing - nambah lan ngurangi. Yen ora bisa nyuda nol, benang panggilan diblokir. Nalika nomer tambah dening sawetara Utas aktif / proses liyane, banjur Utas liwati lan semafore maneh sudo dening nomer liwati. Sampeyan bisa mbayangno sepur ing bottleneck karo semafor. .NET nawakake sawetara konstruksi kanthi fungsi sing padha: AutoResetEvent, ManualResetEvent, Mutex lan aku Semaphore. Kita bakal nggunakake AutoResetEvent, iki minangka konstruksi sing paling gampang: mung rong nilai 0 lan 1 (palsu, bener). Metode dheweke WaitOne() mblokir thread nelpon yen nilai 0, lan yen 1, banjur demotes menyang 0 lan skip. A metode Set() mundhak kanggo 1 lan ngijini siji wong liwat, sing maneh sudo kanggo 0. Tumindak kaya turnstile ing subway.
Ayo dadi rumit solusi lan nggunakake pamblokiran kanggo saben filsuf, lan ora bebarengan. Sing. Saiki sawetara filsuf bisa mangan bebarengan, lan ora mung siji. Nanging maneh mblokir akses menyang meja supaya bisa njupuk garpu kanthi bener, ngindhari kahanan balapan.
Kanggo mangerteni apa sing kedadeyan ing kene, coba dipikirake yen filsuf gagal njupuk garpu, banjur tumindake kaya ing ngisor iki. Dheweke ngenteni akses menyang meja. Sawise nampa, dheweke nyoba njupuk garpu. Ora bisa metu. Iku menehi adoh akses menyang meja (saling pengecualian). Lan dheweke ngliwati "turnstile" (AutoResetEvent) (ing kawitan padha mbukak). Iku tumiba ing siklus maneh, amarga dheweke ora duwe garpu. Dheweke nyoba njupuk lan mandheg ing "turnstile" dheweke. Sawetara pepadhamu sing luwih begja ing sisih tengen utawa kiwa, sawise rampung mangan, bakal mbukak blokir filsuf kita kanthi "mbukak turnstile." Filsuf kita ngliwati (lan nutup ing mburine) kaping pindho. Nyoba kaping telune kanggo njupuk garpu. kasil. Lan dheweke ngliwati turnstile kanggo nedha awan.
Yen ana kesalahan acak ing kode kasebut (padha tansah ana), contone, pepadhamu bakal salah kasebut utawa obyek sing padha bakal digawe AutoResetEvent kanggo kabeh (Enumerable.Repeat), banjur para filsuf bakal ngenteni pangembang, amarga Nemokake kesalahan ing kode kasebut minangka tugas sing angel. Masalah liyane karo solusi iki yaiku ora njamin yen sawetara filsuf ora bakal keluwen.
Solusi hibrida
Kita nyawang loro pendekatan kanggo sinkronisasi, nalika kita tetep ing mode pangguna lan muter ing daur ulang lan nalika kita mblokir thread liwat kernel. Cara pisanan apik kanggo blok sing cendhak, sing kapindho kanggo sing dawa. Asring sampeyan kudu ngenteni sedhela kanggo owah-owahan variabel ing daur ulang, banjur mblokir thread nalika ngenteni dawa. Pendekatan iki dileksanakake ing supaya disebut-. desain hibrida. Nduwe konstruksi sing padha karo mode kernel, nanging saiki nganggo loop mode pangguna: SemaphorSlim, ManualResetEventSlim lsp. Desain sing paling populer ing kene yaiku Monitor, amarga ing C # ana kondhang lock sintaksis. Monitor iki semaphore padha karo Nilai maksimum 1 (mutex), nanging karo support kanggo nunggu ing daur ulang, recursion, Pola Variabel Kondisi (liyane ing ngisor iki), etc.. Ayo kang katon ing solusi karo.
Ing kene maneh mblokir kabeh meja supaya ora ngakses garpu, nanging saiki kita mbukak blokir kabeh benang bebarengan, tinimbang tanggi nalika ana wong sing wis rampung mangan. Sing. Kaping pisanan, ana wong sing mangan lan ngalangi tangga-tanggane, lan yen wis rampung, ana sing kepengin mangan maneh, banjur mlebu ing blok lan nggugah tanggane, amarga wektu nunggu sawijining kurang.
Π£ lock sintaksis duwe sawetara kejutan sing ora nyenengake. Dianjurake kanggo nggunakake Monitor langsung [Richter] [Eric Lippert]. Salah sijine yaiku lock tansah metu Monitor, sanajan ana pangecualian, banjur thread liyane bisa ngganti negara memori sambungan. Ing kasus kaya mengkono, iku asring luwih apik kanggo pindhah menyang deadlock utawa piye wae aman siksa program. Kaget liyane yaiku Monitor nggunakake blok jam (SyncBlock), sing ana ing kabeh obyek. Mulane, yen obyek sing ora cocog dipilih, sampeyan bisa kanthi gampang entuk deadlock (contone, yen sampeyan ngunci senar interned). Kita tansah nggunakake obyek sing didhelikake kanggo iki.
Pola Variabel Kondisi ngidini sampeyan ngleksanakake pangarepan sawetara kondisi sing rumit kanthi luwih ringkes. Ing .NET iku ora lengkap, ing mratelakake panemume, amarga ... Ing teori, kudu ana sawetara antrian ing sawetara variabel (kaya ing Posix Threads), lan ora ing siji kunci. Banjur iku bakal bisa kanggo nggawe kanggo kabeh filsuf. Nanging sanajan ing wangun iki ngidini sampeyan nyepetake kode kasebut.
Akeh filsuf utawa async / await
Oke, saiki kita bisa mblokir thread kanthi efektif. Nanging apa yen kita duwe akeh filsuf? 100? 10000? Contone, kita nampa 100000 panjalukan menyang server web. Nggawe thread kanggo saben request bakal larang, amarga supaya akeh Utas ora bakal kaleksanan ing podo karo. Mung minangka akeh intine logis bakal kaleksanan (Aku duwe 4). Lan wong liya mung bakal njupuk sumber daya. Salah sawijining solusi kanggo masalah iki yaiku pola async / await. Ide kasebut yaiku fungsi ora duwe benang yen kudu ngenteni apa sing diterusake. Lan nalika ana kedadeyan, diterusake eksekusi (nanging ora kudu ing benang sing padha!). Ing kasus kita, kita bakal ngenteni garpu.
SemaphoreSlim wis kanggo iki WaitAsync() cara. Punika implementasine nggunakake pola iki.
Metode karo async / await dijarwakake menyang mesin negara winates licik, kang langsung bali internal Task. Liwat, sampeyan bisa ngenteni cara rampung, mbatalake, lan kabeh sing bisa ditindakake karo Tugas. Ing cara kasebut, mesin negara ngontrol eksekusi. Ing ngisor iki yen ora ana wektu tundha, banjur eksekusi sinkron, lan yen ana, benang kasebut dibebasake. Kanggo luwih ngerti iki, iku luwih apik kanggo dipikir iki mesin negara. Sampeyan bisa nggawe rantai saka iki async / await cara.
Ayo padha nyoba. Karya 100 filsuf ing mesin karo 4 intine logis, 8 detik. Solusi sadurunge karo Monitor mung nglakokake 4 utas pisanan lan ora nglakokake liyane. Saben 4 utas iki nganggur watara 2ms. Lan solusi async / ngenteni nindakake kabeh 100, kanthi rata-rata 6.8 detik saben ngenteni. Mesthine, ing sistem nyata, nganggur nganti 6 detik ora bisa ditampa lan luwih becik ora ngolah panjaluk kanthi cara iki. Solusi karo Monitor ternyata ora bisa diukur.
kesimpulan
Nalika sampeyan bisa ndeleng saka conto cilik iki, ndhukung .NET akeh sinkronisasi mbangun. Nanging, ora mesthi jelas carane nggunakake. Muga-muga artikel iki migunani. Saiki kita mbungkus iki, nanging isih akeh barang sing menarik, contone, koleksi aman thread, TPL Dataflow, program Reaktif, model Transaksi Piranti Lunak, lsp.