Filosfi Well-Fed jew Programmazzjoni .NET Kompetittiva

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.

Filosfi Well-Fed jew Programmazzjoni .NET Kompetittiva

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ħ.

Filosfi Well-Fed jew Programmazzjoni .NET Kompetittiva

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.

Filosfi Well-Fed jew Programmazzjoni .NET Kompetittiva

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ċċ.

Sorsi

Sors: www.habr.com

Żid kumment