Felsefevanên Baş-Fed an Bernameya Pêşbazî ya .NET

Felsefevanên Baş-Fed an Bernameya Pêşbazî ya .NET

Ka em bibînin ka bernamesaziya hevdem û paralel di .Net de çawa dixebite, Pirsgirêka Diningê ya Feylesofan wekî mînak bikar tîne. Plan ev e, ji hevdemkirina têlan / pêvajoyan, heya modela lîstikvan (di beşên jêrîn de). Dibe ku gotar ji bo nasîna yekem an jî ji bo nûvekirina zanîna xwe kêrhatî be.

Çima ew bi tevahî? Transîstor digihîjin mezinahiya xweya herî kêm, qanûna Moore li ser sînorkirina leza ronahiyê radiweste û ji ber vê yekê di hejmarê de zêdebûnek tê dîtin, bêtir transîstor dikarin werin çêkirin. Di heman demê de, hêjeya daneyê mezin dibe, û bikarhêner ji pergalên bersivek tavilê hêvî dikin. Di rewşek weha de, bernameya "normal", dema ku me yek xêzek îcrakar hebe, êdî ne bandor e. Pêdivî ye ku hûn bi rengekî pirsgirêka darvekirina hevdem an hevdemî çareser bikin. Wekî din, ev pirsgirêk di astên cihêreng de heye: di asta têlan de, di asta pêvajoyan de, di asta makîneyên di torê de (pergalên belavbûyî). .NET xwedan teknolojiyên bi kalîte û dem-ceribandinî ye ji bo çareserkirina zû û bi bandor pirsgirêkên weha.

Armanc

Edsger Dijkstra di sala 1965an de ev pirsgirêk ji xwendekarên xwe re aniye ziman. Hejmarek diyar (bi gelemperî pênc) fîlozof û heman jimare fork hene. Ew li ser maseya dor rûdiniştin, di navbera wan de çilmisî. Felsefevan dikarin ji sêlên xwe yên xwarinên bêdawî bixwin, bifikirin an jî li bendê bin. Ji bo ku hûn fîlozofek bixwin, hûn hewce ne ku du qalikan bigirin (ya dawîn çarşexê bi ya yekem re parve dike). Rakirin û danîna qalikê du kiryarên cihê ne. Hemû feylesof bêdeng in. Kar ev e ku meriv algorîtmayek wusa bibîne ku hemî piştî 54 salan jî bifikirin û tijî bibin.

Pêşîn, em hewl bidin ku vê pirsgirêkê bi karanîna cîhek hevbeş çareser bikin. Çerm li ser maseya hevpar radizên û fîlozof bi hêsanî gava ku dibin wan digirin û vedigerînin. Li vir bi hevdemkirinê re pirsgirêk hene, kengê tam meriv surebets bigire? çi dibe bila bibe çilmisî tune? hwd.. Lê pêşî, em dest bi feylesofan bikin.

Ji bo destpêkirina mijaran, em di nav de hewzek tîrêjê bikar tînin Task.Run awa:

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);
}

Hewza Mijarê ji bo xweşkirina çêkirina û jêbirina mijarê hatî çêkirin. Vê hewzê bi peywiran re rêzek heye û CLR li gorî hejmara van peywiran têlan diafirîne an jê dike. Yek hewzek ji bo hemî AppDomains. Ev hewzê divê hema hema her tim bê bikaranîn, ji ber. ne hewce ye ku meriv xwe bi çêkirin, jêbirina têlan, rêzên wan, hwd re mijûl bike. Bêyî hewzek gengaz e, lê hingê divê hûn rasterast bikar bînin Thread, ev ji bo rewşên ku hûn hewce ne ku pêşîniya tîrêjê biguherînin, dema ku me xebatek dirêj heye, ji bo Mijarek Pêşîn, hwd bikêr e.

Bi gotinek din, System.Threading.Tasks.Task pola heman e Thread, lê digel her cûre rehetiyan: şiyana meşandina peywirek li dû bloka karên din, vegerandina wan ji fonksiyonan, bi rehetî qutkirina wan, û hêj bêtir. hwd. Ji bo piştgirîkirina avaniyên asynchron / li benda piştgirîkirina wan hewce ne (Nimûneya Asynchronous-based Task, şekirê hevoksaziyê ji bo li benda operasyonên IO). Em ê li ser vê paşê biaxivin.

CancelationTokenSource li vir pêdivî ye ku xêz bikaribe xwe bi îşareta têla gazîkirinê biqedîne.

Pirsgirêkên hevdemkirinê

Fîlozofên Astengkirî

Baş e, em dizanin ka meriv çawa têlan biafirîne, em hewl bidin ku firavînê bixwin:

// Кто какие вилки взял. К примеру: 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();
    }
}

Li vir em pêşî hewl didin ku çengê çepê, û dûv re jî çengê rastê bistînin, û heke ew biserkeve, wê hingê em dixwin û wan vedigerînin. Girtina yek fork atomî ye, yanî. du têlan di heman demê de nikarin yek bigirin (şaş: ya yekem dixwîne ku fork belaş e, ya duyemîn - jî, ya yekem digire, ya duyemîn digire). Ji bo vê Interlocked.CompareExchange, ya ku divê bi rêwerzek pêvajoyê were bicîh kirin (TSL, XCHG), ku perçeyek bîranînê ji bo xwendin û nivîsandina rêzikên atomî digire. Û SpinWait bi avakirinê re wekhev e while(true) tenê bi piçûkek "efsûnî" - mijar pêvajoyê digire (Thread.SpinWait), lê carinan kontrolê vediguhezîne mijarek din (Thread.Yeild) an jî di xew re diçe (Thread.Sleep).

Lê ev çareserî bi ser nakeve, ji ber herikîn zû (ji bo min di nav saniyeyekê de) têne asteng kirin: hemî fîlozof çenga xwe ya çepê digirin, lê ne ya rast. Dûv re rêzika forks xwedî nirxan e: 1 2 3 4 5.

Felsefevanên Baş-Fed an Bernameya Pêşbazî ya .NET

Di jimarê de, têlên astengkirinê (qedexekirin). Kesk - darvekirin, sor - hevdemkirin, gewr - xêv xew e. Rombus dema destpêkirina Karan destnîşan dikin.

Birçîbûna Feylesofan

Her çend ne hewce ye ku bi taybetî pir xwarinê bifikire, lê birçîbûn dihêle ku kesek dev ji felsefeyê berde. Ka em hewl bidin ku di pirsgirêka xwe de rewşa birçîbûna têlan simule bikin. Birçîbûn ew e dema ku têlek diherike, lê bêyî xebatek girîng, bi gotinek din, ev heman xitimî ye, tenê naha têl xew nake, lê bi aktîvî li tiştek xwarinê digere, lê xwarin tune. Ji bo ku em ji astengkirina pir caran dûr nekevin, ger ku me nikaribî yekî din bigirta em ê qalikê paşde bixin.

// То же что и в 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)
    );
}

Tişta girîng di derbarê vê kodê de ev e ku ji çar fîlozofan du kes ji bîr dikin ku çengê xwe yê çepê deynin. Û derket holê ku ew bêtir xwarinê dixwin, hinên din jî dest bi birçîbûnê dikin, her çend têlan heman pêşîn heye. Li vir ew bi tevahî birçî ne, ji ber. fîlozofên xerab carnan çelenkên xwe paşve didin. Derket holê ku mirovên baş bi qasî 5 qat ji yên nebaş kêmtir dixwin. Ji ber vê yekê xeletiyek piçûk di kodê de dibe sedema daketina performansê. Di vir de jî hêjayî gotinê ye ku rewşek hindik gengaz dibe dema ku hemî fîlozof çenga çepê digirin, rast tune, çepê deynin, bisekinin, dîsa çepê bigirin û hwd. Ev rewş jî birçîbûnek e, bêtir dişibe xitimandinê. Min nekarî dubare bikim. Li jêr wêneyek ji bo rewşek heye ku du fîlozofên xerab her du qalik girtine û du yên baş jî birçî ne.

Felsefevanên Baş-Fed an Bernameya Pêşbazî ya .NET

Li vir hûn dikarin bibînin ku têlan carinan hişyar dibin û hewl didin ku çavkaniyê bigirin. Du ji çar heb tu tiştî nakin (grafika kesk a li jor).

Mirina Fîlozofekî

Welê, pirsgirêkek din a ku dikare şîveke birûmet a fîlozofan qut bike ev e ku yek ji wan ji nişka ve bi çeqilmast di destên wî de bimire (û ew ê wî wusa binax bikin). Wê demê cîran wê bê nîvro bimînin. Hûn dikarin ji bo vê dozê bi xwe kodek mînakek derxînin, mînakî, ew tê avêtin NullReferenceException piştî ku fîlozof qalikan digire. Û, bi awayê, îstîsna dê neyê xebitandin û koda bangê tenê wê negire (ji bo vê AppDomain.CurrentDomain.UnhandledException û hwd.). Ji ber vê yekê, hilgirên xeletiyê di nav mijaran de bixwe û bi bidawîbûna xweş hewce ne.

Bêguman

Baş e, em ê çawa vê pirsgirêkê, birçîbûn û mirinê çareser bikin? Em ê bihêlin ku tenê feylesofek xwe bigihîne qalikan, ji bo vê cîhê veqetandek hevûdu ya têlan zêde bike. Çawa bike? Bihesibînin ku li kêleka fîlozofan garsonek heye ku destûrê dide fîlozofekî ku çeqilmast bigire. Em ê çawa vî garson bikin û fîlozof dê çawa jê bipirsin, pirs balkêş in.

Rêya herî hêsan ew e ku fîlozof bi tenê bi berdewamî ji garsonê bipirsin ku bigihîje çalan. Ewan. êdî fîlozof dê li nêzikî li benda çelekekê nebin, lê li bendê bin an jî ji garsonê bipirsin. Di destpêkê de, em ji bo vê yekê tenê Cihê Bikarhêner bikar tînin, di wê de em navberan bikar naynin da ku hûn prosedurên ji kernelê bang bikin (li ser wan li jêr).

Di cîhê bikarhêner de çareserî

Li vir jî em ê heman tiştî bikin, çawa ku berê bi yek çeng û du fîlozofan re dikir, em ê di çerxekê de bizivirin û li bendê bin. Lê niha ew ê hemû feylesof bin û, wek ku bû, tenê yek çeleng, yanî. mirov dikare bibêje ku tenê fîlozofê ku ev “çengala zêrîn” ji garson girtiye wê bixwe. Ji bo vê em SpinLock bikar tînin.

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 ev astengker e, bi qasî ku tê gotin, heman while(true) { if (!lock) break; }, lê bi hê bêtir "efsûn" ji ya SpinWait (ku li wir tê bikaranîn). Niha ew dizane ku çawa yên li bendê bijmêre, wan piçek xew bike û hê bêtir. hwd.. Bi gelemperî, ji bo xweşbîniyê her tiştî dike. Lê divê em ji bîr mekin ku ev hîn jî heman çerxa çalak e ku çavkaniyên pêvajoyê dixwe û herikînê diparêze, ku dikare bibe sedema birçîbûnê heke yek ji fîlozofan ji yên din pêşengtir bibe, lê xwedan qulikek zêrîn nebe (Pirsgirêka Veguheztina Pêşîn) . Ji ber vê yekê, em wê tenê ji bo guhertinên pir pir kurt ên di bîranîna hevpar de bikar tînin, bêyî bangên sêyemîn, kilîdên hêlîn, û surprîzên din.

Felsefevanên Baş-Fed an Bernameya Pêşbazî ya .NET

Ji bo xêzkirin SpinLock. Çem bi domdarî ji bo çengê zêrîn "şer" dikin. Têkçûn hene - di wêneyê de, qada hilbijartî. Navik bi tevahî nayên bikar anîn: ji hêla van çar mijaran ve tenê 2/3.

Li vir çareseriyek din dê tenê bikar bînin Interlocked.CompareExchange bi heman bendewariya çalak a ku di koda li jor de tê xuyang kirin (di fîlozofên birçî de), lê ev, wekî ku berê jî hatî gotin, dikare ji hêla teorîkî ve bibe sedema astengkirinê.

li ser Interlocked Divê were zanîn ku ne tenê heye CompareExchange, lê her weha rêbazên din ên ji bo xwendin Û nivîsandina atomî. Û bi dûbarekirina guherînê re, heke mijarek din dem hebe ku guheztinên xwe bike (bixwîne 1, bixwîne 2, binivîsîne 2, binivîsîne 1 xirab e), ew dikare ji bo guheztinên tevlihev ên nirxek yekane were bikar anîn (Nimûneya Interlocked Anything) .

Çareseriyên Mode Kernel

Ji bo ku çavkaniyê di çerxekê de winda nekin, ka em bibînin ka em çawa dikarin mijarek asteng bikin. Bi gotineke din, di berdewamiya mînaka xwe de, em binerin ka çawa garson fîlozof dixe xewê û tenê gava hewce dike wî hişyar dike. Pêşîn, em binihêrin ka meriv çawa vê yekê bi moda kernelê ya pergala xebitandinê dike. Hemî strukturên li wir bi gelemperî ji yên di cîhê bikarhêner de hêdîtir in. Ji bo nimûne, çend caran hêdîtir AutoResetEvent dibe ku 53 caran hêdîtir SpinLock [Richter]. Lê bi alîkariya wan, hûn dikarin pêvajoyên li seranserê pergalê, rêvebirin an na hevdem bikin.

Avakirina bingehîn li vir semafor e ku ji hêla Dijkstra ve zêdeyî nîv sedsal berê hatî pêşniyar kirin. Semafor, bi tenê, jimareyek erênî ye ku ji hêla pergalê ve tê rêvebirin, û du operasyonên li ser wê, zêdebûn û kêmbûn e. Ger ew kêm nebe, sifir, wê hingê mijara bangê tê asteng kirin. Dema ku jimar ji hêla hin mijar/pêvajoyek din a çalak ve zêde dibe, wê hingê xêz têne paşguh kirin û semafor dîsa bi hejmara derbasbûyî kêm dibe. Meriv dikare trênan bi semaforê di nav qulikê de xeyal bike. .NET gelek avahîyên bi fonksiyonên wekhev pêşkêşî dike: AutoResetEvent, ManualResetEvent, Mutex û xwe Semaphore. Em ê bikar bînin AutoResetEvent, ev ji van avahiyan ya herî hêsan e: tenê du nirx 0 û 1 (derew, rast). Her Method WaitOne() Ger nirx 0 bû, mijara bangkirinê bloke dike, û heke 1 be, wê dakeve 0-ê û jê berdide. Rêbazek Set() bilind dike 1 û rê dide yek garson derbas bibe, yê ku dîsa dadikeve 0. Mîna gerîdeya metroyê tevdigere.

Werin em çareseriyê tevlihev bikin û qeflê ji bo her fîlozofek bikar bînin, ne ji bo hemîyan bi carekê. Ewan. niha dikare çend fîlozof bi carekê hebin, ne yek. Lê em dîsa gihîştina maseyê asteng dikin da ku bi rast, ji nijadan (şertên pêşbaziyê) dûr bixin, surebets bigirin.

// Для блокирования отдельного философа.
// Инициализируется: 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();
}

Ji bo ku fêm bikin ka li vir çi diqewime, rewşa ku fîlozof nekariye çeqilmast hilgire, wê hingê kiryarên wî dê wiha bin. Ew li benda gihîştina maseyê ye. Piştî ku ew distîne, ew hewl dide ku qalikan bigire. Bi ser neket. Ew gihîştina sifrê dide (derxistina hevdu). Û "turnstila" xwe derbas dike (AutoResetEvent) (ew di destpêkê de vekirî ne). Ew dîsa dikeve dewrê, ji ber çelikên wî tune. Hewl dide wan bigire û li ser "turnstila" xwe disekine. Cîranek bêtir bextewar li rastê an çepê, piştî ku xwarin qedandiye, fîlozofê me vedike, "dora xwe vedike." Feylesofê me cara duduyan jê derbas dibe (û li pişt wê diqede). Ew ji bo cara sêyemîn hewl dide ku çelikan bigire. Bextxweş bî. Û ew dorhêla xwe ji bo xwarinê derbas dike.

Gava ku di kodek weha de xeletiyên rasthatî hebin (ew her gav hene), mînakî, cîranek xelet tête diyar kirin an heman tişt tê afirandin. AutoResetEvent ji bo hemî (Enumerable.Repeat), wê hingê fîlozof dê li benda pêşdebiran bin, ji ber ku Dîtina xeletiyan di kodek weha de karekî pir dijwar e. Pirsgirêkek din a vê çareseriyê ev e ku ew garantî nake ku hin fîlozof birçî nemînin.

Çareseriyên Hybrid

Me li du nêzîkatiyên ji bo demê mêze kir, dema ku em di moda bikarhêner û lûkê de dimînin, û dema ku em têxê di nav kernelê de asteng dikin. Rêbaza yekem ji bo kilîdên kurt, ya duyemîn ji bo yên dirêj baş e. Bi gelemperî pêdivî ye ku meriv pêşî bi kurtî li bendê bimîne ku guhêrbarek di xelekekê de biguhezîne, û dûv re dema ku bendewarî dirêj e, têxê asteng bike. Ev nêzîkatî di nav de tê pêkanîn. strukturên hybrid. Li vir heman avahî hene ku ji bo moda kernelê ne, lê naha bi xelekek moda bikarhêner heye: SemaphorSlim, ManualResetEventSlim hwd sêwirana herî populer li vir e Monitor, ji ber di C# de tê zanîn lock hevoksaziyê. Monitor ev heman semafora bi nirxa herî zêde 1 e (mutex), lê bi piştgirîya li benda bendavê, vegerandin, şêwaza Guherbara Rewşê (li ser vê yekê li jêr) hwd. Ka em bi wê re li çareseriyekê binêrin.

// Спрячем объект для Монитора от всех, чтобы без дедлоков.
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);
    }
}

Li vir em dîsa tevahî tabloyê ji bo gihîştina forksan asteng dikin, lê naha em hemî têlan bi carekê ve bloke dikin, û ne cîranan gava ku kesek xwarinê diqede. Ewan. pêşî, yek dixwe û cîranan asteng dike, û gava ku ev yek diqede, lê di cih de dixwaze dîsa bixwe, ew diçe astengkirinê û cîranên xwe hişyar dike, ji ber ku. dema benda wê kêm e.

Bi vî awayî em ji xitimandin û birçîbûna hinek fîlozofan dûr dikevin. Em ji bo bendewariyek kurt xelekek bikar tînin û ji bo demek dirêj ve têxê asteng dikin. Rakirina her kesî bi yekcarî hêdîtir e ji ya ku tenê cîran were rakirin, wekî di çareseriya bi de AutoResetEvent, lê ferqa divê ne mezin be, ji ber Mijar divê pêşî di moda bikarhêner de bimînin.

У lock hevoksaziyê surprîzên nebaş hene. Pêşniyar bikin ku bikar bînin Monitor rasterast [Richter] [Eric Lippert]. Yek ji wan jî ew e lock her tim ji Monitor, heke îstîsnayek hebe jî, di vê rewşê de mijarek din dikare rewşa bîranîna hevpar biguhezîne. Di rewşên weha de, pir caran çêtir e ku meriv bernameyê biçe xitimandinê an bi rengek ewlehî bernameyê biqedîne. Surprîzek din jî ev e ku Monitor blokên hevdemkirinê bikar tîne (SyncBlock), ku di hemî tiştan de hene. Ji ber vê yekê, heke tiştek neguncayî were hilbijartin, hûn dikarin bi hêsanî xitimandinek bi dest bixin (mînak, heke hûn li ser xêzek hundurîn kilît bikin). Em ji bo vê yekê tiştê ku herdem veşartî bikar tînin.

Nimûneya Guherbara Rewşê dihêle hûn bi kurtahî hêviyên hin şertên tevlihev bicîh bînin. Di .NET de, bi dîtina min, ew ne temam e, ji ber ku di teorîyê de, divê li ser çend guherbaran (wek Mijarên Posix) çend rêz hebin, û ne li ser yek lokê. Wê demê mirov dikare wan ji bo hemû fîlozofan çêbike. Lê tewra di vê formê de, ew dihêle hûn kodê kêm bikin.

gelek fîlozof an async / await

Baş e, naha em dikarin bi bandor mijaran asteng bikin. Lê eger gelek fîlozofên me hebin çi? 100? 10000? Mînakî, me 100000 daxwaz ji servera malperê wergirt. Ji bo her daxwaziyekê çêlekek çêdibe, ji ber ku ew qas mijar dê bi hev re nemeşin. Dê tenê bi qasî ku korikên mentiqî hene bimeşîne (4-yên min hene). Û her kesê din dê tenê çavkaniyan bistînin. Yek çareseriyek ji vê pirsgirêkê re modela async / bendewariyê ye. Fikra wê ev e ku heke pêdivî ye ku ew li benda berdewamkirina tiştek bimîne fonksiyon xêzê nagire. Û gava ku ew tiştek dike, ew pêkanîna xwe ji nû ve dest pê dike (lê ne hewce ye ku li ser heman mijarê!). Di doza me de, em ê li benda forkê bisekinin.

SemaphoreSlim ji bo vê heye WaitAsync() awa. Li vir pêkanînek bi karanîna vê nimûneyê ye.

// Запуск такой же, как раньше. Где-нибудь в программе:
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();
}

Rêbaz bi async / await tê wergerandin makîneyek dewleta xapînok ku tavilê hundurê xwe vedigerîne Task. Bi navgîniya wê, hûn dikarin li benda qedandina rêbazê bisekinin, wê betal bikin, û her tiştê ku hûn dikarin bi Task re bikin. Di hundirê rêbazê de makîneya dewletê înfazê kontrol dike. Rêza jêrîn ev e ku heke dereng nebe, wê hingê darvekirin hevdem e, û heke hebe, wê hingê xêz tê berdan. Ji bo fêhmkirina vê yekê çêtir e ku meriv li vê makîneya dewletê binêre. Hûn dikarin ji van zincîran çêbikin async / await rêbazên.

Werin em ceribandin. Xebata 100 feylesofan li ser makîneyeke bi 4 core mantiqî, 8 saniye. Çareseriya berê ya bi Monitor re tenê 4 mijarên pêşîn dimeşand û yên mayî qet neçû. Her yek ji van 4 têlan bi qasî 2ms bêkar ma. Û çareseriya async / bendewariyê hemî 100, bi navînî li benda her yekê 6.8 saniyeyan. Bê guman, di pergalên rastîn de, bêkar 6 çirke nayê pejirandin û çêtir e ku meriv ewqas daxwazên bi vî rengî neyên kirin. Çareseriya bi Monitorê re derket holê ku bi tevahî ne pîvanbar e.

encamê

Wekî ku hûn ji van mînakên piçûk dibînin, .NET gelek avahiyên hevdemkirinê piştgirî dike. Lêbelê, her gav ne diyar e ka meriv wan çawa bikar tîne. Ez hêvî dikim ku ev gotara alîkar bû. Heya nuha, ev dawî ye, lê hîn jî gelek tiştên balkêş mane, mînakî, berhevokên pêbawer, TPL Dataflow, Bernameya Reaktîf, Modela Danûstandina Nermalavê, hwd.

Çavkaniyên

Source: www.habr.com

Add a comment