Filosfi Well-Fed jew Programmazzjoni .NET Kompetittiva
Ejja naraw kif taħdem l-ipprogrammar konkorrenti u parallel f'.Net, billi tuża l-Philosophers Dining Problem bħala eżempju. Il-pjan huwa dan, mis-sinkronizzazzjoni tal-ħjut / proċessi, sal-mudell tal-attur (fil-partijiet li ġejjin). L-artiklu jista 'jkun utli għall-ewwel familjarità jew sabiex jġedded l-għarfien tiegħek.
Għaliex tagħmel dan kollu? It-transistors jilħqu d-daqs minimu tagħhom, il-liġi ta 'Moore tistrieħ fuq il-limitazzjoni tal-veloċità tad-dawl u għalhekk tiġi osservata żieda fin-numru, jistgħu jsiru aktar transistors. Fl-istess ħin, l-ammont ta 'dejta qed jikber, u l-utenti jistennew rispons immedjat mis-sistemi. F'sitwazzjoni bħal din, l-ipprogrammar "normali", meta jkollna ħajt ta 'eżekuzzjoni wieħed, m'għadux effettiv. Għandek bżonn b'xi mod issolvi l-problema ta 'eżekuzzjoni simultanja jew konkorrenti. Barra minn hekk, din il-problema teżisti f'livelli differenti: fil-livell tal-ħjut, fil-livell tal-proċessi, fil-livell tal-magni fin-netwerk (sistemi distribwiti). .NET għandu teknoloġiji ta' kwalità għolja u ttestjati fil-ħin biex isolvu problemi bħal dawn malajr u b'mod effiċjenti.
Kompitu
Edsger Dijkstra poġġa din il-problema lill-istudenti tiegħu sa mill-1965. Il-formulazzjoni stabbilita hija kif ġej. Hemm ċertu numru (normalment ħamsa) ta’ filosfi u l-istess numru ta’ frieket. Huma joqogħdu fuq mejda tonda, frieket bejniethom. Il-filosfi jistgħu jieklu mill-platti tagħhom ta 'ikel bla tarf, jaħsbu jew jistennew. Biex tiekol filosfu, trid tieħu żewġ frieket (l-aħħar jaqsam il-furketta mal-ewwel). Il-ġbir u t-twaħħil ta 'furketta huma żewġ azzjonijiet separati. Il-filosfi kollha huma siekta. Il-kompitu huwa li jinstab algoritmu bħal dan li kollha kemm huma jaħsbu u jkunu mimlijin anke wara 54 sena.
L-ewwel, ejja nippruvaw insolvu din il-problema permezz tal-użu ta 'spazju kondiviż. Il-frieket jimteddu fuq il-mejda komuni u l-filosfi sempliċiment jeħduhom meta jkunu u jpoġġuhom lura. Hawnhekk hemm problemi bis-sinkronizzazzjoni, meta eżattament tieħu surebets? x'jiġri jekk ma jkunx hemm furketta? eċċ Imma l-ewwel, ejja nibdew il-filosfi.
Biex nibdew il-ħjut, nużaw pool tal-ħajt permezz Task.Run metodu:
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);
}
Il-grupp tal-ħajt huwa ddisinjat biex jottimizza l-ħolqien u t-tħassir tal-ħajt. Dan il-grupp għandu kju b'kompiti u s-CLR joħloq jew ineħħi l-ħjut skont in-numru ta 'dawn il-kompiti. Pool wieħed għall-AppDomains kollha. Din il-pool għandha tintuża kważi dejjem, għaliex. m'hemmx għalfejn tolqot bil-ħolqien, it-tħassir tal-ħjut, il-kjuwijiet tagħhom, eċċ. Huwa possibbli mingħajr pool, iżda mbagħad trid tużaha direttament Thread, dan huwa utli għal każijiet meta jkollok bżonn tibdel il-prijorità ta 'ħajta, meta jkollna operazzjoni twila, għal ħajt ta' tagħrif miksub, eċċ.
Fi kliem ieħor, System.Threading.Tasks.Task klassi hija l-istess Thread, iżda b'kull xorta ta 'konvenjenzi: il-kapaċità li tmexxi kompitu wara blokka ta' kompiti oħra, tirritornahom mill-funzjonijiet, tinterrompihom b'mod konvenjenti, u aktar. eċċ. Huma meħtieġa biex jappoġġjaw kostruzzjonijiet asinkroniċi / jistennew (Task-based Asynchronous Pattern, zokkor sintattiku għall-istennija għal operazzjonijiet IO). Nitkellmu dwar dan aktar tard.
CancelationTokenSource hawn huwa meħtieġ sabiex il-ħajta tista 'tittermina ruħha fis-sinjal tal-ħajta li ssejjaħ.
Kwistjonijiet ta' Sinkronizzazzjoni
Filosfi Imblukkati
Tajjeb, nafu kif noħolqu ħjut, ejja nippruvaw nieklu:
// Кто какие вилки взял. К примеру: 1 1 3 3 - 1й и 3й взяли первые две пары.
private int[] forks = Enumerable.Repeat(0, philosophersAmount).ToArray();
// То же, что RunPhilosopher()
private void RunDeadlock(int i, CancellationToken token)
{
// Ждать вилку, взять её. Эквивалентно:
// while(true)
// if forks[fork] == 0
// forks[fork] = i+1
// break
// Thread.Sleep() или Yield() или SpinWait()
void TakeFork(int fork) =>
SpinWait.SpinUntil(() =>
Interlocked.CompareExchange(ref forks[fork], i+1, 0) == 0);
// Для простоты, но можно с Interlocked.Exchange:
void PutFork(int fork) => forks[fork] = 0;
while (true)
{
TakeFork(Left(i));
TakeFork(Right(i));
eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
PutFork(Left(i));
PutFork(Right(i));
Think(i);
// Завершить работу по-хорошему.
token.ThrowIfCancellationRequested();
}
}
Hawnhekk l-ewwel nippruvaw nieħdu l-furketta tax-xellug, u mbagħad il-furketta tal-lemin, u jekk taħdem, allura nieklu u npoġġuhom lura. Li tieħu furketta waħda hija atomika, i.e. żewġ ħjut ma jistgħux jieħdu waħda fl-istess ħin (skorretta: l-ewwel jaqra li l-furketta hija ħielsa, it-tieni - ukoll, l-ewwel jieħu, it-tieni jieħu). Għal din Interlocked.CompareExchange, li għandha tiġi implimentata bi struzzjoni tal-proċessur (TSL, XCHG), li jillokkja biċċa memorja għall-qari u l-kitba sekwenzjali atomiċi. U SpinWait huwa ekwivalenti għall-kostruzzjoni while(true) biss bi ftit "maġija" - il-ħajta tieħu l-proċessur (Thread.SpinWait), iżda kultant tittrasferixxi l-kontroll għal ħajt ieħor (Thread.Yeild) jew torqod (Thread.Sleep).
Iżda din is-soluzzjoni ma taħdimx, għaliex il-flussi dalwaqt (għalija fi żmien sekonda) huma mblukkati: il-filosfi kollha jieħdu l-furketta tax-xellug tagħhom, iżda mhux dik tal-lemin. Il-firxa tal-frieket imbagħad għandha l-valuri: 1 2 3 4 5.
Fil-figura, l-imblukkar tal-ħjut (deadlock). Aħdar - eżekuzzjoni, aħmar - sinkronizzazzjoni, griż - il-ħajta qed torqod. Ir-rombi jindikaw il-ħin tal-bidu tal-Ħidmiet.
Il-Ġuħ tal-Filosofi
Għalkemm mhux meħtieġ li wieħed jaħseb speċjalment ħafna ikel, iżda l-ġuħ iġġiegħel lil xi ħadd iċedi l-filosofija. Ejja nippruvaw nisimulaw is-sitwazzjoni ta 'ġuħ ta' ħjut fil-problema tagħna. Il-ġuħ huwa meta ħajta tkun qed taħdem, iżda mingħajr xogħol sinifikanti, fi kliem ieħor, dan huwa l-istess staġnar, biss issa l-ħajt mhux qed jorqod, iżda qed ifittex b'mod attiv xi ħaġa x'jiekol, iżda m'hemm l-ebda ikel. Sabiex nevitaw imblukkar frekwenti, aħna se npoġġu l-furketta lura jekk ma nistgħux nieħdu oħra.
// То же что и в RunDeadlock, но теперь кладем вилку назад и добавляем плохих философов.
private void RunStarvation(int i, CancellationToken token)
{
while (true)
{
bool hasTwoForks = false;
var waitTime = TimeSpan.FromMilliseconds(50);
// Плохой философов может уже иметь вилку:
bool hasLeft = forks[Left(i)] == i + 1;
if (hasLeft || TakeFork(Left(i), i + 1, waitTime))
{
if (TakeFork(Right(i), i + 1, TimeSpan.Zero))
hasTwoForks = true;
else
PutFork(Left(i)); // Иногда плохой философ отдает вилку назад.
}
if (!hasTwoForks)
{
if (token.IsCancellationRequested) break;
continue;
}
eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
bool goodPhilosopher = i % 2 == 0;
// А плохой философ забывает положить свою вилку обратно:
if (goodPhilosopher)
PutFork(Left(i));
// А если и правую не положит, то хорошие будут вообще без еды.
PutFork(Right(i));
Think(i);
if (token.IsCancellationRequested)
break;
}
}
// Теперь можно ждать определенное время.
bool TakeFork(int fork, int philosopher, TimeSpan? waitTime = null)
{
return SpinWait.SpinUntil(
() => Interlocked.CompareExchange(ref forks[fork], philosopher, 0) == 0,
waitTime ?? TimeSpan.FromMilliseconds(-1)
);
}
L-importanti dwar dan il-kodiċi huwa li tnejn minn kull erba’ filosfi jinsew ipoġġu l-furketta tax-xellug tagħhom. U jirriżulta li jieklu aktar ikel, filwaqt li oħrajn jibdew imutu bil-ġuħ, għalkemm il-ħjut għandhom l-istess prijorità. Hawnhekk mhumiex kompletament imutu bil-ġuħ, għax. filosofi ħżiena jpoġġu l-frieket tagħhom lura kultant. Jirriżulta li nies tajbin jieklu madwar 5 darbiet inqas minn dawk ħżiena. Allura żball żgħir fil-kodiċi jwassal għal tnaqqis fil-prestazzjoni. Hawn ukoll ta’ min jinnota li sitwazzjoni rari hija possibbli meta l-filosfi kollha jieħdu l-furketta tax-xellug, m’hemmx waħda tal-lemin, poġġew ix-xellug, jistennew, jerġgħu jieħdu x-xellug, eċċ. Din is-sitwazzjoni hija wkoll ġuħ, aktar bħal staġnar. I naqas milli rrepetiha. Hawn taħt hawn stampa għal sitwazzjoni fejn żewġ filosofi ħżiena ħadu ż-żewġ frieket u tnejn tajbin qed imutu bil-ġuħ.
Hawnhekk tista 'tara li l-ħjut iqumu kultant u jippruvaw jiksbu r-riżors. Tnejn mill-erba 'qlub ma jagħmlu xejn (graff aħdar hawn fuq).
Mewt ta’ Filosofu
Ukoll, problema oħra li tista 'tinterrompi pranzu glorjuż ta' filosofi hija jekk wieħed minnhom imut f'daqqa bil-frieket f'idejh (u jidfnuh hekk). Imbagħad il-ġirien jitħallew mingħajr ikla. Tista 'toħroġ b'kodiċi ta' eżempju għal dan il-każ lilek innifsek, pereżempju, jintrema 'l barra NullReferenceException wara li l-filosfu jieħu l-frieket. U, bil-mod, l-eċċezzjoni mhux se tiġi ttrattata u l-kodiċi tas-sejħa mhux se jaqbadha biss (għal dan AppDomain.CurrentDomain.UnhandledException u eċċ.). Għalhekk, jimmaniġġjaw l-iżbalji huma meħtieġa fil-ħjut infushom u b'terminazzjoni graceful.
Wejter
Tajjeb, kif insolvu din l-imblokk, il-ġuħ, u l-problema tal-mewt? Aħna se nħallu filosofu wieħed biss jilħaq il-frieket, żid esklużjoni reċiproka ta 'ħjut għal dan il-post. Kif tagħmel dan? Ejja ngħidu li hemm wejter ħdejn il-filosfi li jagħti permess lil xi filosofu wieħed biex jieħu l-frieket. Kif nagħmlu dan il-wejter u kif il-filosfi se jistaqsuh, il-mistoqsijiet huma interessanti.
L-aktar mod sempliċi huwa meta l-filosfi sempliċement jitolbu kontinwament lill-wejter għall-aċċess għall-frieket. Dawk. issa l-filosfi mhux se jistennew furketta fil-qrib, imma jistennew jew jistaqsu lill-wejter. Għall-ewwel, nużaw biss l-Ispazju tal-Utent għal dan, fih ma nużawx interruzzjonijiet biex insejħu xi proċeduri mill-qalba (dwarhom hawn taħt).
Soluzzjonijiet fl-ispazju tal-utent
Hawnhekk se nagħmlu l-istess kif konna nagħmlu b’furketta waħda u żewġ filosfi, se dduru f’ċiklu u nistennew. Imma issa se jkunu kollha filosfi u, kif kien, furketta waħda biss, i.e. jista’ jingħad li hu biss il-filosfu li ħa din il-“furketta tad-deheb” mingħand il-wejter. Għal dan nużaw SpinLock.
private static SpinLock spinLock = new SpinLock(); // Наш "официант"
private void RunSpinLock(int i, CancellationToken token)
{
while (true)
{
// Взаимная блокировка через busy waiting. Вызываем до try, чтобы
// выбрасить исключение в случае ошибки в самом SpinLock.
bool hasLock = false;
spinLock.Enter(ref hasLock);
try
{
// Здесь может быть только один поток (mutual exclusion).
forks[Left(i)] = i + 1; // Берем вилку сразу, без ожидания.
forks[Right(i)] = i + 1;
eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
forks[Left(i)] = 0;
forks[Right(i)] = 0;
}
finally
{
if(hasLock) spinLock.Exit(); // Избегаем проблемы со смертью философа.
}
Think(i);
if (token.IsCancellationRequested)
break;
}
}
SpinLock dan huwa blocker, ma, bejn wieħed u ieħor, l-istess while(true) { if (!lock) break; }, iżda b'aktar "maġija" milli fi SpinWait (li jintuża hemmhekk). Issa jaf jgħodd lil dawk li qed jistennew, jorqdu ftit, u aktar. eċċ B'mod ġenerali, jagħmel dak kollu possibbli biex jottimizza. Imma rridu niftakru li dan għadu l-istess ċiklu attiv li jiekol ir-riżorsi tal-proċessur u jżomm il-fluss, li jista 'jwassal għal ġuħ jekk wieħed mill-filosfi jsir aktar prijorità minn oħrajn, iżda m'għandux furketta tad-deheb (problema ta' Inverżjoni ta 'Prijorità) . Għalhekk, nużawha biss għal bidliet qosra ħafna fil-memorja kondiviża, mingħajr ebda sejħiet ta 'partijiet terzi, serraturi nested, u sorpriżi oħra.
Tpinġija għal SpinLock. Il-flussi huma kontinwament "jiġġieldu" għall-furketta tad-deheb. Hemm fallimenti - fil-figura, iż-żona magħżula. Il-qlub mhumiex utilizzati bis-sħiħ: madwar 2/3 biss minn dawn l-erba 'ħjut.
Soluzzjoni oħra hawnhekk tkun li tuża biss Interlocked.CompareExchange bl-istess stennija attiva kif muri fil-kodiċi ta 'hawn fuq (fil-filosfi starving), iżda dan, kif diġà ntqal, jista' teoretikament iwassal għall-imblukkar.
fuq Interlocked Għandu jiġi nnutat li hemm mhux biss CompareExchange, iżda wkoll metodi oħra għall-qari u l-kitba atomiċi. U permezz tar-ripetizzjoni tal-bidla, f'każ li ħajt ieħor ikollu ħin biex jagħmel il-bidliet tiegħu (aqra 1, aqra 2, ikteb 2, ikteb 1 huwa ħażin), jista 'jintuża għal bidliet kumplessi għal valur wieħed (mudell Interlocked Anything) .
Soluzzjonijiet tal-Modalità Kernel
Biex tevita ħela ta' riżorsi f'linja, ejja naraw kif nistgħu nibblukkaw ħajta. Fi kliem ieħor, inkomplu bl-eżempju tagħna, ejja naraw kif il-wejter jorqod lill-filosfu u jqajmuh biss meta jkun meħtieġ. L-ewwel, ejja nħarsu lejn kif tagħmel dan permezz tal-modalità kernel tas-sistema operattiva. L-istrutturi kollha hemm ħafna drabi huma aktar bil-mod minn dawk fl-ispazju tal-utent. Bosta drabi aktar bil-mod, pereżempju AutoResetEvent forsi 53 darba aktar bil-mod SpinLock [Richter]. Iżda bl-għajnuna tagħhom, tista 'tissinkronizza proċessi fis-sistema kollha, ġestiti jew le.
Il-kostrutt bażiku hawnhekk huwa s-semaforu propost minn Dijkstra aktar minn nofs seklu ilu. Semaforu huwa, sempliċiment, numru sħiħ pożittiv immexxi mis-sistema, u żewġ operazzjonijiet fuqu, inkrement u tnaqqis. Jekk tonqos milli tonqos, żero, allura l-ħajta tas-sejħa hija mblukkata. Meta n-numru jiżdied b'xi ħajt/proċess attiv ieħor, allura l-ħjut jinqabżu u s-semaforu jerġa' jitnaqqas bin-numru mgħoddi. Wieħed jista 'jimmaġina ferroviji f'konġestjoni b'semaforu. .NET joffri diversi kostruzzjonijiet b'funzjonalità simili: AutoResetEvent, ManualResetEvent, Mutex u jien stess Semaphore. Aħna se nużaw AutoResetEvent, din hija l-aktar sempliċi minn dawn il-kostruzzjonijiet: żewġ valuri biss 0 u 1 (falza, vera). Il-Metodu Tagħha WaitOne() jimblokka l-ħajt tas-sejħa jekk il-valur kien 0, u jekk 1, inaqqasha għal 0 u taqbeżha. Metodu Set() jgħolli għal 1 u jħalli wejter wieħed jgħaddi, li jerġa' jbaxxi għal 0. Jaġixxi bħal turnstile tas-subway.
Ejja nikkomplikaw is-soluzzjoni u nużaw is-serratura għal kull filosofu, u mhux għal kulħadd f'daqqa. Dawk. issa jista’ jkun hemm diversi filosofi f’daqqa, u mhux wieħed. Imma nerġgħu nibblukkaw l-aċċess għat-tabella sabiex b'mod korrett, nevitaw it-tiġrijiet (kundizzjonijiet tar-razza), nieħdu surebets.
// Для блокирования отдельного философа.
// Инициализируется: new AutoResetEvent(true) для каждого.
private AutoResetEvent[] philosopherEvents;
// Для доступа к вилкам / доступ к столу.
private AutoResetEvent tableEvent = new AutoResetEvent(true);
// Рождение философа.
public void Run(int i, CancellationToken token)
{
while (true)
{
TakeForks(i); // Ждет вилки.
// Обед. Может быть и дольше.
eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
PutForks(i); // Отдать вилки и разблокировать соседей.
Think(i);
if (token.IsCancellationRequested) break;
}
}
// Ожидать вилки в блокировке.
void TakeForks(int i)
{
bool hasForks = false;
while (!hasForks) // Попробовать еще раз (блокировка не здесь).
{
// Исключающий доступ к столу, без гонок за вилками.
tableEvent.WaitOne();
if (forks[Left(i)] == 0 && forks[Right(i)] == 0)
forks[Left(i)] = forks[Right(i)] = i + 1;
hasForks = forks[Left(i)] == i + 1 && forks[Right(i)] == i + 1;
if (hasForks)
// Теперь философ поест, выйдет из цикла. Если Set
// вызван дважды, то значение true.
philosopherEvents[i].Set();
// Разблокировать одного ожидающего. После него значение tableEvent в false.
tableEvent.Set();
// Если имеет true, не блокируется, а если false, то будет ждать Set от соседа.
philosopherEvents[i].WaitOne();
}
}
// Отдать вилки и разблокировать соседей.
void PutForks(int i)
{
tableEvent.WaitOne(); // Без гонок за вилками.
forks[Left(i)] = 0;
// Пробудить левого, а потом и правого соседа, либо AutoResetEvent в true.
philosopherEvents[LeftPhilosopher(i)].Set();
forks[Right(i)] = 0;
philosopherEvents[RightPhilosopher(i)].Set();
tableEvent.Set();
}
Biex tifhem dak li qed jiġri hawn, ikkunsidra l-każ meta l-filosfu naqas milli jieħu l-frieket, allura l-azzjonijiet tiegħu se jkunu kif ġej. Huwa qed jistenna l-aċċess għall-mejda. Wara li rċievaha, jipprova jieħu l-frieket. Ma ħadmitx. Jagħti aċċess għat-tabella (esklużjoni reċiproka). U jgħaddi "turnstile" tiegħu (AutoResetEvent) (inizjalment huma miftuħa). Jidħol fiċ-ċiklu mill-ġdid, għax m’għandux frieket. Jipprova jeħodhom u jieqaf fil-“turnstile” tiegħu. Xi ġar aktar ixxurtjat fuq il-lemin jew ix-xellug, wara li spiċċa jiekol, jiftaħ il-filosfu tagħna, "tiftaħ it-turnstile tiegħu." Il-filosfu tagħna jgħaddiha (u tagħlaq warajha) għat-tieni darba. Jipprova għat-tielet darba jieħu l-frieket. Ix-xorti t-tajba. U jgħaddi turnstile tiegħu biex jieklu.
Meta jkun hemm żbalji każwali f'tali kodiċi (dejjem jeżistu), pereżempju, ġar jiġi speċifikat ħażin jew jinħoloq l-istess oġġett AutoResetEvent għal kulħadd (Enumerable.Repeat), allura l-filosfi se jkunu qed jistennew l-iżviluppaturi, għaliex Is-sejba ta 'żbalji f'tali kodiċi hija kompitu pjuttost diffiċli. Problema oħra b’din is-soluzzjoni hija li ma tiggarantixxix li xi filosofu ma jmurx bil-ġuħ.
Soluzzjonijiet Ibridi
Ħaresna lejn żewġ approċċi għall-ħin, meta nibqgħu fil-modalità tal-utent u l-linja, u meta nibblukkaw il-ħajt permezz tal-qalba. L-ewwel metodu huwa tajjeb għal serraturi qosra, it-tieni għal dawk twal. Ħafna drabi huwa meħtieġ li l-ewwel tistenna fil-qosor għal varjabbli biex tinbidel f'linja, u mbagħad timblokka l-ħajta meta l-istennija tkun twila. Dan l-approċċ huwa implimentat fl-hekk imsejħa. strutturi ibridi. Hawn huma l-istess kostruzzjonijiet bħall-modalità kernel, iżda issa b'linja tal-modalità tal-utent: SemaphorSlim, ManualResetEventSlim eċċ Id-disinn l-aktar popolari hawnhekk huwa Monitor, għax f'C# hemm magħruf lock sintassi. Monitor dan huwa l-istess semaforu b'valur massimu ta '1 (mutex), iżda b'appoġġ għall-istennija f'linja, rikorsi, il-mudell Kundizzjoni Varjabbli (aktar dwar dak hawn taħt), eċċ Ejja nħarsu lejn soluzzjoni magħha.
// Спрячем объект для Монитора от всех, чтобы без дедлоков.
private readonly object _lock = new object();
// Время ожидания потока.
private DateTime?[] _waitTimes = new DateTime?[philosophersAmount];
public void Run(int i, CancellationToken token)
{
while (true)
{
TakeForks(i);
eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
PutForks(i);
Think(i);
if (token.IsCancellationRequested) break;
}
}
// Наше сложное условие для Condition Variable паттерна.
bool CanIEat(int i)
{
// Если есть вилки:
if (forks[Left(i)] != 0 && forks[Right(i)] != 0)
return false;
var now = DateTime.Now;
// Может, если соседи не более голодные, чем текущий.
foreach(var p in new int[] {LeftPhilosopher(i), RightPhilosopher(i)})
if (_waitTimes[p] != null && now - _waitTimes[p] > now - _waitTimes[i])
return false;
return true;
}
void TakeForks(int i)
{
// Зайти в Монитор. То же самое: lock(_lock) {..}.
// Вызываем вне try, чтобы возможное исключение выбрасывалось выше.
bool lockTaken = false;
Monitor.Enter(_lock, ref lockTaken);
try
{
_waitTimes[i] = DateTime.Now;
// Condition Variable паттерн. Освобождаем лок, если не выполненно
// сложное условие. И ждем пока кто-нибудь сделает Pulse / PulseAll.
while (!CanIEat(i))
Monitor.Wait(_lock);
forks[Left(i)] = i + 1;
forks[Right(i)] = i + 1;
_waitTimes[i] = null;
}
finally
{
if (lockTaken) Monitor.Exit(_lock);
}
}
void PutForks(int i)
{
// То же самое: lock (_lock) {..}.
bool lockTaken = false;
Monitor.Enter(_lock, ref lockTaken);
try
{
forks[Left(i)] = 0;
forks[Right(i)] = 0;
// Освободить все потоки в очереди ПОСЛЕ вызова Monitor.Exit.
Monitor.PulseAll(_lock);
}
finally
{
if (lockTaken) Monitor.Exit(_lock);
}
}
Hawnhekk qed nerġgħu nibblukkaw il-mejda kollha għall-aċċess għall-frieket, iżda issa qed niżblokkaw il-ħjut kollha f'daqqa, u mhux ġirien meta xi ħadd jispiċċa jiekol. Dawk. l-ewwel, xi ħadd jiekol u jimblokka l-ġirien, u meta dan xi ħadd jispiċċa, imma jrid jerġa jiekol mill-ewwel, jidħol fl-imblukkar u jqum lill-ġirien tiegħu, għax. il-ħin ta' stennija tiegħu huwa inqas.
Hekk nevitaw l-imblokk u l-ġuħ ta’ xi filosofu. Aħna nużaw linja għal stennija qasira u nibblukkaw il-ħajta għal waħda twila. L-iżblokk ta 'kulħadd f'daqqa huwa aktar bil-mod milli kieku l-ġar biss kien żblokkat, bħal fis-soluzzjoni ma' AutoResetEvent, iżda d-differenza m'għandhiex tkun kbira, għaliex il-ħjut għandhom jibqgħu fil-modalità tal-utent l-ewwel.
У lock sintassi għandha sorpriżi koroh. Irrakkomanda li tuża Monitor direttament [Richter] [Eric Lippert]. Waħda minnhom hija dik lock dejjem barra minn Monitor, anki jekk kien hemm eċċezzjoni, f'liema każ ħajt ieħor jista 'jbiddel l-istat tal-memorja kondiviża. F'każijiet bħal dawn, ħafna drabi huwa aħjar li tmur f'imblokk jew b'xi mod sikur tittermina l-programm. Sorpriża oħra hija li Monitor juża blokki ta 'sinkronizzazzjoni (SyncBlock), li huma preżenti fl-oġġetti kollha. Għalhekk, jekk jintgħażel oġġett mhux xieraq, tista 'faċilment tikseb deadlock (per eżempju, jekk tissakkar fuq spag internat). Aħna nużaw l-oġġett dejjem moħbi għal dan.
Il-mudell Varjabbli tal-Kundizzjoni jippermettilek li timplimenta b'mod aktar konċiż l-aspettattiva ta 'xi kundizzjoni kumplessa. F'.NET, mhux komplut, fl-opinjoni tiegħi, għaliex fit-teorija, għandu jkun hemm diversi kjuwijiet fuq diversi varjabbli (bħal fil-Ħjut Posix), u mhux fuq lok wieħed. Imbagħad wieħed jista 'jagħmilhom għall-filosfi kollha. Iżda anke f'din il-forma, jippermettilek tnaqqas il-kodiċi.
ħafna filosfi jew async / await
Okay, issa nistgħu nibblukkaw il-ħjut b'mod effettiv. Imma x’jiġri jekk ikollna ħafna filosfi? 100? 10000? Pereżempju, irċevejna 100000 talba lis-server tal-web. Se jkun overhead li jinħoloq ħajt għal kull talba, għaliex tant ħjut mhux se jimxu b'mod parallel. Se jimxu biss kemm hemm qlub loġiċi (għandi 4). U kulħadd se jneħħi biss ir-riżorsi. Soluzzjoni waħda għal din il-problema hija l-mudell async / await. L-idea tagħha hija li l-funzjoni ma żżommx il-ħajta jekk trid tistenna li xi ħaġa tkompli. U meta tagħmel xi ħaġa, terġa 'tibda l-eżekuzzjoni tagħha (iżda mhux neċessarjament fuq l-istess ħajta!). Fil-każ tagħna, se nistennew il-furketta.
SemaphoreSlim għandha għal dan WaitAsync() metodu. Hawnhekk hawn implimentazzjoni li tuża dan il-mudell.
// Запуск такой же, как раньше. Где-нибудь в программе:
Task.Run(() => Run(i, cancelTokenSource.Token));
// Запуск философа.
// Ключевое слово async -- компилятор транслирует этот метот в асинхронный.
public async Task Run(int i, CancellationToken token)
{
while (true)
{
// await -- будем ожидать какого-то события.
await TakeForks(i);
// После await, продолжение возможно в другом потоке.
eatenFood[i] = (eatenFood[i] + 1) % (int.MaxValue - 1);
// Может быть несколько событий для ожидания.
await PutForks(i);
Think(i);
if (token.IsCancellationRequested) break;
}
}
async Task TakeForks(int i)
{
bool hasForks = false;
while (!hasForks)
{
// Взаимоисключающий доступ к столу:
await _tableSemaphore.WaitAsync();
if (forks[Left(i)] == 0 && forks[Right(i)] == 0)
{
forks[Left(i)] = i+1;
forks[Right(i)] = i+1;
hasForks = true;
}
_tableSemaphore.Release();
// Будем ожидать, чтобы сосед положил вилки:
if (!hasForks)
await _philosopherSemaphores[i].WaitAsync();
}
}
// Ждем доступа к столу и кладем вилки.
async Task PutForks(int i)
{
await _tableSemaphore.WaitAsync();
forks[Left(i)] = 0;
// "Пробудить" соседей, если они "спали".
_philosopherSemaphores[LeftPhilosopher(i)].Release();
forks[Right(i)] = 0;
_philosopherSemaphores[RightPhilosopher(i)].Release();
_tableSemaphore.Release();
}
Metodu ma async / await hija tradotta f'magna tal-istat delikata li immedjatament tirritorna l-intern tagħha Task. Permezz tiegħu, tista 'tistenna għat-tlestija tal-metodu, tikkanċellah, u kull ħaġa oħra li tista' tagħmel b'Task. Ġewwa l-metodu, il-magna tal-istat tikkontrolla l-eżekuzzjoni. L-aħħar linja hija li jekk ma jkunx hemm dewmien, allura l-eżekuzzjoni hija sinkronika, u jekk ikun hemm, allura l-ħajta tiġi rilaxxata. Għal fehim aħjar ta 'dan, huwa aħjar li tħares lejn din il-magna tal-istat. Tista 'toħloq ktajjen minn dawn async / await metodi.
Ejja nittestjaw. Xogħol ta’ 100 filosofu fuq magna b’4 qlub loġiċi, 8 sekondi. Is-soluzzjoni preċedenti bil-Monitor dam biss l-ewwel 4 ħjut u l-bqija ma damu xejn. Kull wieħed minn dawn l-4 ħjut kien idle għal madwar 2ms. U s-soluzzjoni async / await dam mal-100 kollha, b'stennija medja ta '6.8 sekondi kull wieħed. Naturalment, f'sistemi reali, idle għal 6 sekondi huwa inaċċettabbli u huwa aħjar li ma tipproċessax tant talbiet bħal dan. Is-soluzzjoni bil-Monitor irriżulta li mhi skalabbli xejn.
Konklużjoni
Kif tistgħu taraw minn dawn l-eżempji żgħar, .NET jappoġġja ħafna kostruzzjonijiet ta 'sinkronizzazzjoni. Madankollu, mhux dejjem huwa ovvju kif tużahom. Nispera li dan l-artikolu kien ta 'għajnuna. Għalissa, dan huwa t-tmiem, iżda għad fadal ħafna affarijiet interessanti, pereżempju, kollezzjonijiet bla periklu għall-ħajt, TPL Dataflow, Programmazzjoni reattiva, mudell ta 'Transazzjoni tas-Software, eċċ.