Файласуфҳои хуб ғарқшуда ё барномасозии .NET рақобатпазир

Файласуфҳои хуб ғарқшуда ё барномасозии .NET рақобатпазир

Биёед бубинем, ки чӣ гуна барномасозии ҳамзамон ва параллелӣ дар .Net кор мекунад, бо истифода аз мисоли мушкилоти lunching philosophers. Нақша чунин аст, аз синхронизатсияи ришта / раванд то модели актёр (дар қисмҳои зерин). Мақола метавонад барои шиносоии аввал ё нав кардани дониши шумо муфид бошад.

Чаро ҳатто медонед, ки чӣ тавр ин корро кардан лозим аст? Транзисторҳо ба андозаи ҳадди ақали худ расида истодаанд, қонуни Мур ба ҳадди суръати рӯшноӣ мерасад ва аз ин рӯ афзоиш дар адад мушоҳида мешавад; транзисторҳои бештар сохтан мумкин аст. Дар айни замон, ҳаҷми маълумот меафзояд ва корбарон аз системаҳо посухи фаврӣ интизоранд. Дар чунин вазъият, барномасозии "муқаррарӣ", вақте ки мо як риштаи иҷрокунанда дорем, дигар муассир нест. Ба мо лозим аст, ки бо ягон рох масъалаи якзайл ё якзайл ичро кардани онро хал кунем. Ғайр аз он, ин мушкилот дар сатҳҳои гуногун вуҷуд дорад: дар сатҳи ришта, дар сатҳи раванд, дар сатҳи мошинҳо дар шабака (системаҳои тақсимшуда). .NET дорои технологияҳои баландсифат ва дар вақт санҷидашуда барои зуд ва самаранок ҳалли чунин мушкилот мебошад.

Мақсад

Эдгер Дейкстра ин масъаларо ҳанӯз соли 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);
}

Ҳавзи ришта барои оптимизатсияи эҷод ва нест кардани риштаҳо пешбинӣ шудааст. Ин ҳавз як қатор вазифаҳо дорад ва CLR вобаста ба шумораи ин вазифаҳо риштаҳоро эҷод ё нест мекунад. Як ҳавз барои ҳама AppDomains. Ин ҳавз бояд қариб ҳамеша истифода шавад, зеро... лозим нест, ки бо эҷод ва нест кардани риштаҳо, навбатҳои онҳо ва ғайра ташвиш кашед. Шумо метавонед ин корро бидуни ҳавз анҷом диҳед, аммо пас шумо бояд онро мустақиман истифода баред Thread, ин барои ҳолатҳое муфид аст, ки мо бояд афзалияти риштаро тағир диҳем, вақте ки мо амалиёти тӯлонӣ дорем, барои риштаи Foreground ва ғайра.

Ба ибораи дигар, System.Threading.Tasks.Task синф ҳамон аст Thread, аммо бо ҳама гуна қулайҳо: қобилияти оғоз кардани вазифа пас аз блоки дигар вазифаҳо, баргардонидани онҳо аз функсияҳо, ба таври қулай қатъ кардани онҳо ва ғайра. ва ғайра. Онҳо барои дастгирии конструксияҳои асинхронӣ/интизорӣ лозиманд (Шакли асинхронии ба вазифа асосёфта, шакари синтаксисӣ барои интизории амалиёти IO). Мо дар ин бора баъдтар гап мезанем.

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), ки як пораи хотираро барои хондан ва навиштани пайдарпайи атомӣ қулф мекунад. Ва SpinWait ба сохтмон баробар аст while(true) танҳо бо каме "ҷодугарӣ" - ришта протсессорро мегирад (Thread.SpinWait), аммо баъзан назоратро ба риштаи дигар мегузарад (Thread.Yeild) ё хоб меравад (Thread.Sleep).

Аммо ин роҳи ҳалли масъала кор намекунад, зеро... риштаҳо ба зудӣ (дар давоми як сония барои ман) баста мешаванд: ҳама файласуфон чанголи чапи худро мегиранд, аммо рост нест. Пас, массиви форкҳо дорои чунин арзишҳо мебошанд: 1 2 3 4 5.

Файласуфҳои хуб ғарқшуда ё барномасозии .NET рақобатпазир

Дар расм, бастани риштаҳо (сарбаста). Сабз нишонаи иҷро, сурх синхронизатсия ва хокистарӣ нишон медиҳад, ки ришта хоб аст. Алмосҳо вақти оғози вазифаҳоро нишон медиҳанд.

Гуруснагии файласуфон

Ҳарчанд барои фикр кардан ба шумо ғизои зиёд лозим нест, гуруснагӣ метавонад ҳар касро маҷбур кунад, ки аз фалсафа даст кашад. Биёед кӯшиш кунем, ки вазъияти гуруснагии риштаро дар мушкилоти худ тақлид кунем. Гуруснагӣ вақте аст, ки ришта кор мекунад, аммо бидуни кори назаррас, ба ибораи дигар, ин ҳамон бунбаст аст, танҳо ҳоло ришта хоб намекунад, аммо фаъолона чизе меҷӯяд, аммо ғизо нест. Барои он ки зуд-зуд банд нашавед, агар дигарашро гирифта натавонем, чангакро баргардонем.

// То же что и в 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 маротиба камтар мехӯранд. Ҳамин тавр, хатои хурд дар код боиси паст шудани кор мегардад. Дар ин ҷо ҳам бояд қайд кард, ки як ҳолати нодир имконпазир аст, ки ҳама файласуфон чанголи чапро мегиранд, рост нест, чапро мегузоранд, интизор мешаванд, чапро боз мегиранд ва ғайра. Ин ҳолат низ гуруснагӣ аст, бештар ба бастани мутақобила монанд аст. Ман онро такрор карда натавонистам. Дар зер тасвире барои вазъияте оварда шудааст, ки ду файласуфи бад ҳарду чангакро гирифтаанд ва ду файласуфи хуб гуруснагӣ мекашанд.

Файласуфҳои хуб ғарқшуда ё барномасозии .NET рақобатпазир

Дар ин ҷо шумо мебинед, ки риштаҳо баъзан бедор мешаванд ва кӯшиш мекунанд, ки манбаъ гиранд. Аз чор ядро ​​дутоаш ҳеҷ кор намекунад (графики сабз дар боло).

Марги файласуф

Хуб, як мушкили дигар, ки зиёфати пурифтихори файласуфонро халалдор карда метавонад, ин аст, ки яке аз онҳо бо чангак дар даст ногаҳон бимирад (ва ӯро ҳамин тавр ба хок супоранд). Он гох хамсояхо бе хуроки нисфирузй мемонанд. Шумо метавонед худатон як рамзи мисолеро барои ин парванда пайдо кунед, масалан, онро партофтаанд NullReferenceException пас аз он ки файласуф чанголхоро мегирад. Ва, дар омади гап, истисно коркард карда намешавад ва рамзи занг на танҳо онро дастгир мекунад (барои ин AppDomain.CurrentDomain.UnhandledException ва ғайра). Аз ин рӯ, коркардкунандагони хатогиҳо дар худи риштаҳо ва бо қатъкунии зебо лозиманд.

Waiter

Хуб, мо ин мушкили бунбаст, гуруснагӣ ва маргро чӣ гуна ҳал кунем? Мо танҳо як файласуфро ба чангалҳо иҷозат медиҳем ва барои ин ҷой истиснои мутақобилаи риштаҳоро илова мекунем. Чӣ тавр бояд кард? Фарз мекунем, ки дар паҳлӯи файласуфон пешхидмате ҳаст, ки ба як файласуф иҷозат медиҳад, ки чангакҳоро бигирад. Чӣ гуна бояд ин пешхизматро созем ва файласуфон аз ӯ чӣ гуна мепурсанд, саволҳои ҷолибанд.

Роҳи соддатарин барои файласуфон ин аст, ки ҳамеша аз пешхизмат барои дастрасӣ ба вилкаҳо дархост кунанд. Онхое. Акнун файласуфон дар наздикии он вилка интизор намешаванд, балки мунтазир мешаванд ё аз пешхизмат мепурсанд. Дар аввал мо барои ин танҳо Фазои корбарро истифода мебарем; дар он мо барои даъват кардани ягон расмиёт аз ядро ​​(маълумот дар бораи онҳо дар поён) аз танаффус истифода намебарем.

Ҳалли фазои корбар

Дар ин ҷо мо ҳамон кореро, ки қаблан бо як чангол ва ду файласуф карда будем, мекунем, дар як ҳалқа меронем ва интизор мешавем. Аммо ҳоло он ҳама файласуфон хоҳад буд ва ба тавре ки гӯё танҳо як чангак, яъне. гуфтан мумкин аст, ки фацат файласуф, ки аз пешхизмат ин «чанги тиллой»-ро гирифтааст, мехурад. Барои ин мо 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 ин блокатор аст, бо, тақрибан гӯем, ҳамон while(true) { if (!lock) break; }, балки бо «ҷодугарӣ» ҳатто бештар аз дар SpinWait (ки дар он ҷо истифода мешавад). Акнун вай медонад, ки чӣ тавр интизориҳоро ҳисоб кунад, онҳоро каме хоб кунад ва ғайра. ва ғайра Дар маҷмӯъ, он ҳама кори имконпазир барои оптимизатсия мекунад. Аммо мо бояд дар хотир дошта бошем, ки ин ҳамон ҳалқаи фаъолест, ки захираҳои протсессориро мехӯрад ва риштаро нигоҳ медорад, ки метавонад ба гуруснагӣ оварда расонад, агар яке аз файласуфон нисбат ба дигарон авлавият пайдо кунад, аммо чароғаки тиллоӣ надошта бошад (проблемаи Priority Inversion). ). Аз ин рӯ, мо онро танҳо барои тағироти хеле кӯтоҳ дар хотираи муштарак бидуни зангҳои тарафи сеюм, қулфҳои лона ва дигар сюрпризҳо истифода мебарем.

Файласуфҳои хуб ғарқшуда ё барномасозии .NET рақобатпазир

Расм барои SpinLock. Дарёҳо барои гиреҳи тиллоӣ пайваста «мубориза» мекунанд. Нокомиҳо ба амал меоянд - майдони қайдшуда дар расм. Корҳо пурра истифода намешаванд: танҳо тақрибан 2/3 аз ин чор ришта.

Як ҳалли дигар дар ин ҷо танҳо истифода мешавад Interlocked.CompareExchange бо ҳамон интизории фаъол, ки дар коди боло нишон дода шудааст (дар файласуфони гурусна), аммо ин, тавре ки аллакай гуфта шудааст, аз ҷиҳати назариявӣ метавонад ба басташавӣ оварда расонад.

ба Interlocked гуфтан бамаврид аст, ки на танхо CompareExchange, балки инчунин усулҳои дигари хондан ва навиштани атомӣ. Ва бо такрор кардани тағирот, агар риштаи дигар тағиротҳои худро ворид кунад (хонед 1, хонед 2, нависед 2, нависед 1 бад аст), онро барои тағироти мураккаб ба як арзиш истифода бурдан мумкин аст (Шабакаи Interlocked Anything).

Ҳалли режими ядро ​​​​

Барои роҳ надодан ба беҳуда сарф кардани захираҳо дар як ҳалқа, биёед бубинем, ки чӣ тавр бастани ришта. Яъне, мисоли худро идома дода, бубинем, ки пешхизмат файласуфро чӣ гуна хоб мебарад ва танҳо ҳангоми зарурат ӯро бедор мекунад. Аввалан, биёед бубинем, ки чӣ тавр ин корро тавассути режими ядрои системаи оператсионӣ анҷом дод. Ҳама сохторҳо дар он ҷо аксар вақт нисбат ба сохторҳои фазои корбар сусттар мешаванд. Масалан, якчанд маротиба сусттар кунед AutoResetEvent шояд 53 маротиба сусттар бошад SpinLock [Рихтер]. Аммо бо ёрии онҳо, шумо метавонед равандҳоро дар тамоми система ҳамоҳанг созед, идора карда мешавад ё не.

Тарҳи асосии ин ҷо семафорест, ки аз ҷониби Дижкстра зиёда аз ним аср пеш пешниҳод карда шудааст. Семафор, содда карда гӯем, як адади мусбатест, ки аз ҷониби система идора карда мешавад ва ду амал дар он - афзоиш ва камшавӣ. Агар сифрро кам кардан имконнопазир бошад, пас риштаи занг баста мешавад. Вақте ки шумора бо ягон риштаи дигар/раванди фаъол зиёд мешавад, он гоҳ риштаҳо гузаронида мешаванд ва семафор боз ба миқдори гузашта кам мешавад. Шумо метавонед қаторҳоро дар тангно бо семафор тасаввур кунед. .NET якчанд конструксияҳоро бо функсияҳои шабеҳ пешниҳод мекунад: 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, зеро дар C# як чизи маъруф вуҷуд дорад lock синтаксис. Monitor ин ҳамон семафор бо арзиши максималии 1 (mutex), аммо бо дастгирии интизорӣ дар давр, рекурсия, Намунаи тағирёбандаи шарт (бештар дар бораи он дар зер) ва ғайра. Биёед ҳалли онро бо он бубинем.

// Спрячем объект для Монитора от всех, чтобы без дедлоков.
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, ҳатто агар истисно вуҷуд дошта бошад ва он гоҳ риштаи дигар метавонад ҳолати хотираи муштаракро тағир диҳад. Дар чунин ҳолатҳо, беҳтар аст, ки ба бунбаст равед ё ба таври бехатар барномаро қатъ кунед. Дигар тааҷҷубовар ин аст, ки Monitor блокҳои соатро истифода мебарад (SyncBlock), ки дар хамаи объектхо мавчуданд. Аз ин рӯ, агар объекти номуносиб интихоб карда шавад, шумо метавонед ба осонӣ бунбастро ба даст оред (масалан, агар шумо сатри интернатро қулф кунед). Барои ин мо ҳамеша объекти пинҳоншударо истифода мебарем.

Намунаи тағирёбандаи шарт ба шумо имкон медиҳад, ки интизории баъзе ҳолати мураккабро дақиқтар иҷро кунед. Дар .NET он нопурра аст, ба назари ман, зеро... Дар назария, дар якчанд тағирёбанда (ба мисли Posix Threads) бояд якчанд навбат бошад, на дар як қулф. Он гох онхоро барои хамаи файласуфон сохтан мумкин мебуд. Аммо ҳатто дар ин шакл он ба шумо имкон медиҳад, ки кодро кӯтоҳ кунед.

Бисёр файласуфон ё async / await

Хуб, ҳоло мо метавонем риштаҳоро ба таври муассир маҳкам кунем. Аммо агар мо файласуфони зиёд дошта бошем-чй? 100? 10000? Масалан, мо ба сервери веб 100000 4 дархост гирифтем. Эҷоди ришта барои ҳар як дархост гарон хоҳад буд, зеро ин кадар риштахо дар баробари ичро карда намешаванд. Танҳо ҳамон қадар ядроҳои мантиқӣ иҷро карда мешаванд (ман XNUMX дорам). Ва ҳар каси дигар танҳо захираҳоро аз даст медиҳанд. Як роҳи ҳалли ин мушкилот намунаи асинх/интизор аст. Идеяи он аз он иборат аст, ки функсия риштаро нигоҳ намедорад, агар он барои идомаи чизе интизор шавад. Ва ҳангоме ки чизе рӯй медиҳад, он иҷрои худро идома медиҳад (вале на ҳатман дар ҳамон ришта!). Дар ҳолати мо, мо як чангакро интизор мешавем.

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 сония. Ҳалли қаблӣ бо Monitor танҳо 4 риштаи аввалро иҷро кард ва боқимондаро тамоман иҷро накард. Ҳар яке аз ин 4 ришта тақрибан 2ms бекор буд. Ва ҳалли асинхронӣ / интизорӣ ҳама 100-ро иҷро кард, ки ҳар як интизорӣ ба ҳисоби миёна 6.8 сонияро ташкил медиҳад. Албатта, дар системаҳои воқеӣ 6 сония бекор мондан ғайри қобили қабул аст ва беҳтар аст, ки ин қадар дархостҳоро ин тавр коркард накунед. Ҳалли бо Monitor тамоман миқёспазир набуд.

хулоса

Тавре ки шумо аз ин мисолҳои хурд мебинед, .NET бисёр сохторҳои ҳамоҳангсозиро дастгирӣ мекунад. Бо вуҷуди ин, на ҳама вақт маълум аст, ки чӣ тавр истифода бурдани онҳо. Ман умедворам, ки ин мақола муфид буд. Мо ҳоло инро ҷамъбаст карда истодаем, аммо то ҳол чизҳои ҷолиби зиёде боқӣ мондаанд, масалан, коллексияҳои аз ришта бехатар, Dataflow TPL, барномасозии реактивӣ, модели транзаксияҳои нармафзор ва ғайра.

Манбаъҳои иттилоот

Манбаъ: will.com

Илова Эзоҳ