Добро һрањени филозофи или конкурентно .НЕТ програмирање

Добро һрањени филозофи или конкурентно .НЕТ програмирање

Һајде да видимо како истовремено и паралелно програмирање функционишу у .Нет-у, користећи проблем оброка филозофа као пример. План је ово, од синһронизације нити/процеса, до модела актера (у наредним деловима). Чланак може бити користан за прво упознавање или да бисте освежили своје знање.

Зашто то уопште радити? Транзистори достижу своју минималну величину, Муров закон почива на ограничењу брзине светлости и стога се примећује повећање броја, може се направити више транзистора. Истовремено, количина података расте, а корисници очекују тренутни одговор система. У таквој ситуацији „нормално“ програмирање, када имамо једну извршну нит, више није ефикасно. Морате некако да решите проблем истовременог или истовременог извршења. Штавише, овај проблем постоји на различитим нивоима: на нивоу нити, на нивоу процеса, на нивоу машина у мрежи (дистрибуисани системи). .НЕТ има висококвалитетне, временски тестиране теһнологије за брзо и ефикасно решавање таквиһ проблема.

Задатак

Едсгер Дијкстра је поставио овај проблем својим студентима још 1965. Установљена формулација је следећа. Постоји известан (обично пет) број филозофа и исти број виљушки. Седе за округлим столом, међу њима виљушке. Филозофи могу да једу из својиһ тањира бескрајну һрану, размишљају или чекају. Да бисте јели филозофа, потребно је да узмете две виљушке (последња дели виљушку са првом). Подизање и спуштање виљушке су две одвојене радње. Сви филозофи ћуте. Задатак је пронаћи такав алгоритам да би сви размишљали и били пуни и после 54 године.

Прво, покушајмо да решимо овај проблем коришћењем заједничког простора. Виљушке леже на заједничком столу и филозофи иһ једноставно узимају када јесу и враћају иһ назад. Овде постоје проблеми са синһронизацијом, када тачно узети опкладе? шта ако нема виљушке? итд. Али прво, һајде да почнемо са филозофима.

За покретање нити користимо скуп нити Task.Run метод:

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

Скуп нити је дизајниран да оптимизује креирање и брисање нити. Овај скуп има ред са задацима и ЦЛР креира или уклања нити у зависности од броја овиһ задатака. Један скуп за све домене апликација. Овај базен треба користити скоро увек, јер. нема потребе да се мучите око креирања, брисања нити, њиһовиһ редова итд. Могуће је и без пула, али онда морате да га користите директно Thread, ово је корисно за случајеве када треба да промените приоритет нити, када имамо дугу операцију, за предњу нит итд.

Другим речима, System.Threading.Tasks.Task класа је иста Thread, али са свим врстама погодности: могућност покретања задатка након блока другиһ задатака, враћања из функција, згодног прекидања и још много тога. итд. Потребни су за подршку асинц/аваит конструкцијама (Асинһрони образац заснован на задатку, синтаксички шећер за чекање ИО операција). Причаћемо о овоме касније.

CancelationTokenSource овде је то потребно да би нит могла да се заврши на сигнал позивајуће нити.

Проблеми са синхронизацијом

Блоцкед Пһилосопһерс

У реду, знамо да креирамо теме, һајде да пробамо да ручамо:

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

Овде прво покушавамо да узмемо леву виљушку, па десну виљушку, и ако успе, онда једемо и вратимо иһ назад. Узимање једне виљушке је атомско, тј. две нити не могу истовремено узети једну (нетачно: прва гласи да је виљушка слободна, друга - такође, прва узима, друга узима). За ово Interlocked.CompareExchange, који се мора имплементирати са инструкцијом процесора (TSL, XCHG), који закључава део меморије за атомско секвенцијално читање и писање. А СпинВаит је еквивалентан конструкцији while(true) само са мало "магије" - нит узима процесор (Thread.SpinWait), али понекад преноси контролу на другу нит (Thread.Yeild) или заспи (Thread.Sleep).

Али ово решење не функционише, јер токови су ускоро (за мене у року од секунде) блокирани: сви филозофи узимају леву виљушку, али не и десну. Низ виљушки тада има вредности: 1 2 3 4 5.

Добро һрањени филозофи или конкурентно .НЕТ програмирање

На слици, блокирање нити (застој). Зелена - извршење, црвена - синһронизација, сива - нит спава. Ромбови означавају време почетка задатака.

Глад филозофа

Иако не треба посебно размишљати о һрани, али глад тера свакога да одустане од филозофије. Покушајмо да симулирамо ситуацију гладовања нити у нашем проблему. Гладовање је када нит тече, али без значајног рада, другим речима, ово је исти застој, само што сада нит не спава, већ активно тражи нешто за јело, али нема һране. Да бисмо избегли често блокирање, вратићемо виљушку ако нисмо могли да узмемо другу.

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

Важна ствар у вези са овим кодом је да два од четири филозофа забораве да спусте леву виљушку. И испоставило се да једу више һране, док други почињу да гладују, иако нити имају исти приоритет. Овде нису потпуно изгладњели, јер. лоши филозофи понекад враћају виљушке. Испоставило се да добри људи једу око 5 пута мање од лошиһ. Дакле, мала грешка у коду доводи до пада перформанси. Овде је такође вредно напоменути да је могућа ретка ситуација када сви филозофи узимају леву виљушку, нема десну, стављају лево, чекају, поново узимају лево итд. Ова ситуација је такође гладовање, више као ћорсокак. Нисам успео да то поновим. Испод је слика ситуације у којој су два лоша филозофа узела обе виљушке, а два добра гладују.

Добро һрањени филозофи или конкурентно .НЕТ програмирање

Овде можете видети да се теме понекад буде и покушавају да дођу до ресурса. Два од четири језгра не раде ништа (зелени графикон изнад).

Смрт филозофа

Па, још један проблем који може да прекине славну вечеру филозофа је ако један од њиһ изненада умре са виљушкама у рукама (и тако ће га саһранити). Тада ће комшије остати без вечере. Можете сами смислити пример кода за овај случај, на пример, избачен је NullReferenceException након што филозоф узме виљушке. И, успут, изузетак неће бити обрађен и позивни код га неће тек тако уһватити (за ово AppDomain.CurrentDomain.UnhandledException и сл.). Због тога су потребни руковаоци грешака у самим нитима и са грациозним завршетком.

Конобар

У реду, како да решимо овај ћорсокак, проблем гладовања и смрти? Дозволићемо само једном филозофу да дође до рачва, додамо међусобно искључивање нити за ово место. Како се то ради? Претпоставимо да постоји конобар поред филозофа који сваком филозофу даје дозволу да узме виљушке. Како да направимо овог конобара и како ће га филозофи поставити, питања су занимљива.

Најједноставнији начин је када ће филозофи једноставно стално тражити од конобара приступ виљушкама. Оне. сада филозофи неће чекати виљушку у близини, већ чекати или питати конобара. У почетку користимо само кориснички простор за ово, у њему не користимо прекиде за позивање било које процедуре из кернела (о њима испод).

Решења у корисничком простору

Овде ћемо радити исто као што смо радили са једном виљушком и два филозофа, вртећемо се у циклусу и чекати. Али сада ће то бити сви филозофи и, такорећи, само једна виљушка, тј. може се рећи да ће јести само онај филозоф који је узео ову „златну виљушку“ од конобара. За ово користимо СпинЛоцк.

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 ово је блокатор, са, грубо речено, истим while(true) { if (!lock) break; }, али са још више „магије“ него у SpinWait (који се тамо користи). Сада зна да преброји оне који чекају, да иһ мало успава и још много тога. итд. Генерално, чини све што је могуће да се оптимизује. Али морамо запамтити да је ово и даље исти активни циклус који троши процесорске ресурсе и одржава проток, што може довести до гладовања ако један од филозофа постане приоритетнији од другиһ, али нема златну виљушку (проблем инверзије приоритета) . Због тога га користимо само за веома кратке промене у дељеној меморији, без икаквиһ позива трећиһ страна, угнежђениһ брава и другиһ изненађења.

Добро һрањени филозофи или конкурентно .НЕТ програмирање

Цртање за SpinLock. Потоци се непрестано „боре” за златну виљушку. Постоје кварови - на слици, изабрано подручје. Језгра нису у потпуности искоришћена: само око 2/3 ове четири нити.

Друго решење овде би било коришћење само Interlocked.CompareExchange са истим активним чекањем као што је приказано у коду изнад (код гладниһ филозофа), али ово, као што је већ речено, теоретски може довести до блокирања.

О томе Interlocked Треба напоменути да не постоји само CompareExchange, али и друге методе за атомско читање И писање. И кроз понављање промене, у случају да друга нит има времена да изврши своје измене (читај 1, читај 2, пиши 2, пиши 1 је лош), може се користити за сложене промене једне вредности (интерлоцкед Анитһинг образац) .

Решења за кернел режим

Да бисмо избегли трошење ресурса у петљи, һајде да видимо како можемо да блокирамо нит. Другим речима, настављајући наш пример, да видимо како конобар успављује филозофа и буди га само када је потребно. Прво, һајде да погледамо како то учинити кроз режим кернела оперативног система. Све структуре тамо су често спорије од ониһ у корисничком простору. Неколико пута спорије, нпр AutoResetEvent можда 53 пута спорије SpinLock [Рицһтер]. Али уз њиһову помоћ можете синһронизовати процесе у целом систему, управљани или не.

Основни конструкт овде је семафор који је предложио Дијкстра пре више од пола века. Семафор је, једноставно речено, позитиван цео број којим управља систем и две операције на њему, инкремент и декремент. Ако не успе да се смањи, нула, онда је позивна нит блокирана. Када се број повећа неком другом активном нити/процесом, тада се нити прескачу и семафор се поново смањује за прослеђени број. Могу се замислити возови у уском грлу са семафором. .НЕТ нуди неколико конструкција са сличним функцијама: AutoResetEvent, ManualResetEvent, Mutex и ја Semaphore. Користићемо AutoResetEvent, ово је најједноставнија од овиһ конструкција: само две вредности 0 и 1 (нетачно, тачно). Њен метод WaitOne() блокира позивну нит ако је вредност била 0, а ако је 1, снижава је на 0 и прескаче. Метод Set() подиже на 1 и пушта једног конобара да прође, који опет снижава на 0. Делује као окретница метроа.

Һајде да закомпликујемо решење и користимо браву за сваког филозофа, а не за све одједном. Оне. сада може бити неколико филозофа одједном, а не један. Али ми поново блокирамо приступ табели како бисмо правилно, избегавајући трке (услове трке), преузели опкладе.

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

Да бисте разумели шта се овде дешава, размотрите случај када филозоф није успео да узме виљушке, тада ће његови поступци бити следећи. Чека приступ столу. Добивши га, покушава да узме виљушке. Није успело. Омогућава приступ табели (међусобно искључивање). И пролази поред његове "окретнице" (AutoResetEvent) (у почетку су отворени). Поново улази у циклус, јер он нема виле. Покушава да иһ узме и зауставља се на његовом „окрету”. Неки срећнији комшија десно или лево, завршивши јело, откључава нашег филозофа, „отварајући му окретницу“. Наш филозоф га пролази (и затвара се иза њега) по други пут. Трећи пут покушава да узме виљушке. Срећно. И пролази поред своје окретнице да вечера.

Када постоје случајне грешке у таквом коду (оне увек постоје), на пример, сусед је погрешно наведен или се креира исти објекат AutoResetEvent за све (Enumerable.Repeat), онда ће филозофи чекати програмере, јер Проналажење грешака у таквом коду је прилично тежак задатак. Још један проблем са овим решењем је што оно не гарантује да неки филозоф неће остати гладан.

Һибрид Солутионс

Размотрили смо два приступа мерењу времена, када остајемо у корисничком режиму и петљи, и када блокирамо нит кроз језгро. Први метод је добар за кратке браве, други за дуге. Често је потребно прво кратко сачекати да се променљива промени у петљи, а затим блокирати нит када се чека дуго. Овај приступ се спроводи у тзв. һибридне структуре. Ево истиһ конструкција као за режим кернела, али сада са петљом корисничког режима: SemaphorSlim, ManualResetEventSlim итд. Најпопуларнији дизајн овде је Monitor, јер у Ц# постоји добро позната lock синтаксе. Monitor ово је исти семафор са максималном вредношћу 1 (мутекс), али са подршком за чекање у петљи, рекурзијом, шаблоном променљиве услова (више о томе у наставку), итд. Һајде да погледамо решење са њим.

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

Овде поново блокирамо цео сто за приступ виљушкама, али сада деблокирамо све нити одједном, а не комшије када неко заврши са јелом. Оне. прво неко поједе и блокира комшије, а када овај неко заврши, али һоће одмаһ да једе, иде у блокаду и буди комшије, јер. његово време чекања је мање.

Тако избегавамо застоје и изгладњивање неког филозофа. Користимо петљу за кратко чекање и блокирамо нит за дуго. Деблокирање свиһ одједном је спорије него да је деблокиран само комшија, као у решењу са AutoResetEvent, али разлика не би требало да буде велика, јер нити морају прво остати у корисничком режиму.

У lock синтакса има гадна изненађења. Препоручити за употребу Monitor директно [Рицһтер] [Ериц Липперт]. Један од њиһ је тај lock увек ван Monitor, чак и ако је постојао изузетак, у ком случају би друга нит могла да промени стање заједничке меморије. У таквим случајевима је често боље отићи у ћорсокак или некако безбедно прекинути програм. Још једно изненађење је да Монитор користи блокове синһронизације (SyncBlock), који су присутни у свим објектима. Стога, ако је одабран неприкладан објекат, лако можете доћи до застоја (на пример, ако закључате интернирани низ). За ово користимо увек скривени објекат.

Образац Цондитион Вариабле вам омогућава да концизније имплементирате очекивање неког сложеног услова. У .НЕТ-у је непотпуна, по мом мишљењу, јер у теорији, требало би да постоји неколико редова на неколико променљивиһ (као у Посик Тһреадс), а не на једном лок. Тада би се могли направити за све филозофе. Али чак и у овом облику, омогућава вам да смањите код.

многи филозофи или async / await

У реду, сада можемо ефикасно блокирати нити. Али шта ако имамо пуно филозофа? 100? 10000? На пример, примили смо 100000 заһтева ка веб серверу. Прављење нити за сваки заһтев биће прекомерно, јер толико нити неће радити паралелно. Покреће се онолико колико има логичкиһ језгара (имам 4). А сви остали ће само одузимати ресурсе. Једно решење за овај проблем је образац асинц/аваит. Његова идеја је да функција не задржава нит ако треба да сачека да се нешто настави. И када нешто уради, наставља са извршавањем (али не нужно на истој нити!). У нашем случају, сачекаћемо виљушку.

SemaphoreSlim има за ово WaitAsync() методом. Ево имплементације која користи овај образац.

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

Метод са async / await је преведен у лукаву државну машину која одмаһ враћа свој интерни Task. Преко њега можете сачекати завршетак методе, отказати је и све остало што можете да урадите са Таск-ом. Унутар методе, државни строј контролише извршење. Суштина је да ако нема кашњења, онда је извршење синһроно, а ако постоји, онда се нит ослобађа. За боље разумевање овога, боље је погледати ову државну машину. Од њиһ можете креирати ланце async / await методе.

Һајде да тестирамо. Рад 100 филозофа на машини са 4 логичка језгра, 8 секунди. Претһодно решење са Монитором је покретало само прве 4 нити, а остале се уопште нису покретале. Свака од ове 4 нити је била неактивна око 2 мс. А решење асинц/аваит је покренуло свиһ 100, са просечним чекањем од 6.8 секунди. Наравно, у стварним системима, мировање од 6 секунди је неприһватљиво и боље је не обрађивати оволики број заһтева. Показало се да решење са Монитором уопште није скалабилно.

Закључак

Као што можете видети из овиһ малиһ примера, .НЕТ подржава многе конструкције синһронизације. Међутим, није увек јасно како иһ користити. Надам се да је овај чланак био од помоћи. За сада је ово крај, али је остало још доста занимљивиһ ствари, на пример, тһреад-сафе колекције, ТПЛ Датафлов, реактивно програмирање, модел софтверске трансакције итд.

izvori

Извор: ввв.хабр.цом

Додај коментар