Feallsanaich le deagh bhiadh no prògramadh farpaiseach .NET

Feallsanaich le deagh bhiadh no prògramadh farpaiseach .NET

Feuch gum faic sinn mar a tha prògramadh co-shìnte agus co-shìnte ag obair ann an .Net, a 'cleachdadh Duilgheadas Biadh Feallsanachd mar eisimpleir. Is e am plana seo, bho shioncronachadh snàithleanan / phròiseasan, gu modail an cleasaiche (anns na pàirtean a leanas). Faodaidh an artaigil a bhith feumail don chiad neach-eòlais no gus an eòlas agad ùrachadh.

Carson a nì thu e idir? Bidh transistors a’ ruighinn am meud as ìsle, tha lagh Moore an urra ri cuingealachadh astar an t-solais agus mar sin thathas a’ faicinn àrdachadh anns an àireamh, faodar barrachd transistors a dhèanamh. Aig an aon àm, tha an ìre de dhàta a 'fàs, agus tha luchd-cleachdaidh an dùil freagairt sa bhad bho na siostaman. Ann an suidheachadh mar sin, chan eil prògramadh “àbhaisteach”, nuair a bhios aon snàithlean gnìomhachaidh againn, èifeachdach tuilleadh. Feumaidh tu dòigh air choireigin fuasgladh fhaighinn air duilgheadas cur gu bàs aig an aon àm no aig an aon àm. A bharrachd air an sin, tha an duilgheadas seo ann aig diofar ìrean: aig ìre snàithleanan, aig ìre phròiseasan, aig ìre innealan anns an lìonra (siostaman cuairteachaidh). Tha teicneòlasan àrd-inbhe, le deagh ùine aig NET airson fuasgladh fhaighinn air duilgheadasan mar sin gu luath agus gu h-èifeachdach.

Amas

Thog Edsger Dijkstra an duilgheadas seo dha na h-oileanaich aige cho tràth ri 1965. Tha an cruthachadh stèidhichte mar a leanas. Tha àireamh sònraichte (mar as trice còig) de fheallsanaich agus an aon àireamh de forcaichean. Shuidh iad aig bòrd cruinn, forcaichean eatorra. Faodaidh feallsanaich ithe bho na truinnsearan aca de bhiadh gun chrìoch, smaoineachadh no feitheamh. Gus feallsanaiche ithe, feumaidh tu dà fhorc a ghabhail (bidh an tè mu dheireadh a 'roinn a' phòca leis a 'chiad fhear). Tha dà ghnìomh eadar-dhealaichte ann a bhith a’ togail agus a’ cur sìos forc. Tha na feallsanaich uile sàmhach. Is e an obair a leithid de algairim a lorg a bhiodh iad uile a’ smaoineachadh agus a bhith làn eadhon às deidh 54 bliadhna.

An toiseach, feuchaidh sinn ris an duilgheadas seo fhuasgladh le bhith a’ cleachdadh àite co-roinnte. Tha na forcaichean nan laighe air a’ bhòrd chumanta agus bidh na feallsanaich dìreach gan toirt nuair a tha iad agus gan cur air ais. An seo tha duilgheadasan ann le sioncronadh, cuin a bu chòir dhut surebets a ghabhail? dè mura h-eil forc ann? msaa Ach an toiseach, leig leinn tòiseachadh air na feallsanaich.

Gus snàithleanan a thòiseachadh, bidh sinn a 'cleachdadh amar snàithlean troimhe Task.Run dòigh-obrach:

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

Tha an cruinneachadh snàithlean air a dhealbhadh gus cruthachadh snàithlean agus cuir às dha as fheàrr. Tha ciudha aig an amar seo le gnìomhan agus bidh an CLR a’ cruthachadh no a’ toirt air falbh snàithleanan a rèir àireamh nan gnìomhan sin. Aon amar airson a h-uile AppDomains. Bu chòir an amar seo a chleachdadh cha mhòr an-còmhnaidh, oir. chan eil feum air dragh a chuir air cruthachadh, cuir às do snàithleanan, na ciudhaichean aca, msaa. Tha e comasach às aonais amar, ach feumaidh tu a chleachdadh gu dìreach Thread, tha seo feumail airson cùisean nuair a dh'fheumas tu prìomhachas snàithlean atharrachadh, nuair a bhios obrachadh fada againn, airson snàithlean Foreground, msaa.

Ann am faclan eile, System.Threading.Tasks.Task clas an aon rud Thread, ach le gach seòrsa goireas: an comas gnìomh a ruith às deidh bloc de ghnìomhan eile, an toirt air ais bho ghnìomhan, stad a chuir orra gu goireasach, agus barrachd. msaa. Bruidhnidh sinn mu dheidhinn seo nas fhaide air adhart.

CancelationTokenSource an seo tha feum air gus an urrainn don t-snàthainn crìoch a chuir air fhèin aig comharra an t-snàthainn gairm.

Cùisean Sioncronaidh

Feallsanaich air am bacadh

Gu ceart, tha fios againn mar a chruthaicheas sinn snàithleanan, feuchaidh sinn ri lòn a ghabhail:

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

An seo bidh sinn an-toiseach a’ feuchainn ris a’ forc chlì a ghabhail, agus an uairsin am forc cheart, agus ma dh’ obraicheas e a-mach, bidh sinn ag ithe agus gan cur air ais. Tha gabhail aon forc atamach, i.e. chan urrainn dha dà snàithlean aon a ghabhail aig an aon àm (ceàrr: tha a 'chiad fhear a' leughadh gu bheil am forc an-asgaidh, an dàrna fear - cuideachd, tha a 'chiad fhear a' gabhail, an dàrna fear). Airson seo Interlocked.CompareExchange, a dh'fheumar a chur an gnìomh le stiùireadh pròiseasar (TSL, XCHG), a ghlasas pìos cuimhne airson leughadh agus sgrìobhadh sreath atamach. Agus tha SpinWait co-ionann ris an togail while(true) dìreach le beagan "draoidheachd" - bidh an snàthainn a 'toirt a' phròiseasar (Thread.SpinWait), ach uaireannan a’ gluasad smachd gu snàithlean eile (Thread.Yeild) no a' tuiteam na chadal (Thread.Sleep).

Ach chan eil am fuasgladh seo ag obair, oir tha na sruthan gu h-aithghearr (dhomhsa an taobh a stigh de dhiog) air am bacadh : tha na feallsanaich uile a' gabhail an fhorc chlì, ach chan e an tè cheart. Tha na luachan aig an t-sreath forc an uairsin: 1 2 3 4 5.

Feallsanaich le deagh bhiadh no prògramadh farpaiseach .NET

Anns an fhigear, a 'bacadh snàithleanan (deadlock). Uaine - cur gu bàs, dearg - sioncronadh, liath - tha an snàithlean a 'cadal. Tha na rhombuses a’ comharrachadh àm tòiseachaidh Tasks.

Ocras nam Feallsanach

Ged nach eil e riatanach a bhith a 'smaoineachadh gu h-àraidh mòran bìdh, ach tha an t-acras a' toirt air duine sam bith a bhith a 'toirt seachad feallsanachd. Feuchaidh sinn ri atharrais a dhèanamh air suidheachadh gort nan snàithleanan nar duilgheadas. Is e an t-acras nuair a bhios snàithlean a 'ruith, ach gun obair chudromach, ann am faclan eile, is e seo an aon fhuasgladh, dìreach a-nis chan eil an t-snàthainn a' cadal, ach tha e gu gnìomhach a 'coimhead airson rudeigin ri ithe, ach chan eil biadh ann. Gus bacadh tric a sheachnadh, cuiridh sinn am forc air ais mura b 'urrainn dhuinn fear eile a ghabhail.

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

Is e an rud cudthromach mun chòd seo gu bheil dithis a-mach à ceathrar fheallsanaich a’ dìochuimhneachadh am forc chlì a chuir sìos. Agus tha e a 'tionndadh a-mach gu bheil iad ag ithe barrachd bìdh, agus cuid eile a' tòiseachadh leis an acras, ged a tha an aon phrìomhachas aig na snàithleanan. An seo chan eil iad gu tur leis an acras, oir. bidh droch fheallsanaich a’ cur am forc air ais uaireannan. Tha e a 'tionndadh a-mach gu bheil daoine math ag ithe mu 5 tursan nas lugha na an fheadhainn dona. Mar sin tha mearachd bheag sa chòd a’ leantainn gu lùghdachadh ann an coileanadh. Is fhiach a bhith mothachail an seo cuideachd gu bheil suidheachadh tearc comasach nuair a bhios a h-uile feallsanaiche a ’toirt am forc chlì, chan eil fear ceart ann, chuir iad an taobh chlì, feitheamh, gabh an taobh chlì a-rithist, msaa. Tha an suidheachadh seo cuideachd na acras, nas coltaiche ri suidheachadh marbh. Dh'fhàilig mi a-rithist e. Gu h-ìosal tha dealbh airson suidheachadh far a bheil dithis droch fheallsanaiche air an dà chuid forc a ghabhail agus dithis mhath leis an acras.

Feallsanaich le deagh bhiadh no prògramadh farpaiseach .NET

An seo chì thu gu bheil na snàithleanan a 'dùsgadh uaireannan agus a' feuchainn ris a 'ghoireas fhaighinn. Chan eil dhà de na ceithir coraichean a’ dèanamh dad (graf uaine gu h-àrd).

Bàs Feallsanaiche

Uill, is e duilgheadas eile a dh ’fhaodadh stad a chuir air dìnnear glòrmhor de fheallsanaich ma bhàsaicheas fear dhiubh gu h-obann le forcaichean na làmhan (agus adhlaicidh iad e mar sin). An uairsin bidh na nàbaidhean air am fàgail gun lòn. Faodaidh tu fhèin còd eisimpleir a chruthachadh airson a 'chùis seo, mar eisimpleir, tha e air a thilgeil a-mach NullReferenceException an dèidh don fheallsanaiche na forcaichean a ghabhail. Agus, leis an t-slighe, cha tèid an eisgeachd a làimhseachadh agus cha bhith an còd gairm dìreach ga ghlacadh (airson seo AppDomain.CurrentDomain.UnhandledException agus msaa). Mar sin, tha feum air làimhseachadh mhearachdan anns na snàithleanan fhèin agus le crìochnachadh gràsmhor.

Waiter

Ceart gu leòr, ciamar a dh’ fhuasglas sinn an duilgheadas so-leònte, acras is bàis seo? Leigidh sinn le aon fheallsanaiche na forcaichean a ruighinn, cuir às do chèile snàithleanan airson an àite seo. Ciamar a dhèanamh? Osbarr gu bheil fear-frithealaidh ri taobh nam feallsanaich a bheir cead do aon fheallsanaiche na forcaichean a ghabhail. Ciamar a nì sinn an neach-frithealaidh seo agus mar a dh'fhaighnicheas feallsanachd dha, tha na ceistean inntinneach.

Is e an dòigh as sìmplidh nuair a bhios na feallsanaich dìreach an-còmhnaidh ag iarraidh air an neach-frithealaidh faighinn gu na forcaichean. An fheadhainn sin. a nis cha 'n fhan feallsanaich ri forc am fagus, ach feith, no feòraichibh d' an fhear-frithealaidh. An toiseach, cha bhith sinn a’ cleachdadh ach User Space airson seo, ann an sin cha bhith sinn a’ cleachdadh brisidhean gus modhan-obrach sam bith a ghairm bhon kernel (mu dheidhinn gu h-ìosal).

Fuasglaidhean ann an àite luchd-cleachdaidh

An seo nì sinn an aon rud mar a b’ àbhaist dhuinn a dhèanamh le aon forc agus dà fheallsanaiche, bidh sinn a’ snìomh ann an cearcall agus a’ feitheamh. Ach a-nis bidh e uile na fheallsanaiche agus, mar gum biodh, dìreach aon forc, i.e. faodar a ràdh nach ith ach am feallsanach a thug am “forc òir” seo bhon fhear-frithealaidh. Airson seo bidh sinn a’ cleachdadh 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 tha seo na neach-bacadh, le, gu ìre mhòr a 'bruidhinn, an aon rud while(true) { if (!lock) break; }, ach le eadhon barrachd "draoidheachd" na ann an SpinWait (a thathas a’ cleachdadh an sin). A-nis tha fios aige mar a chunntadh an fheadhainn a tha a 'feitheamh, gan cur gu cadal beagan, agus barrachd. etc. Anns an fharsaingeachd, a 'dèanamh a h-uile rud as urrainn a dhèanamh gus an fheum as fheàrr. Ach feumaidh sinn cuimhneachadh gur e seo fhathast an aon chearcall gnìomhach a bhios ag ithe suas goireasan giullachd agus a ’cumail an t-sruth, a dh’ fhaodadh leantainn gu acras ma thig fear de na feallsanaich gu bhith na phrìomhachas na cuid eile, ach nach eil forc òir aige (duilgheadas tionndaidh Prìomhachais) . Mar sin, bidh sinn ga chleachdadh dìreach airson atharrachaidhean glè ghoirid ann an cuimhne co-roinnte, às aonais fiosan treas-phàrtaidh, glasan neadachaidh agus iongnadh eile.

Feallsanaich le deagh bhiadh no prògramadh farpaiseach .NET

A ' tarraing airson SpinLock. Tha na sruthan an-còmhnaidh "a 'sabaid" airson a' phòr òir. Tha fàilligidhean ann - anns an fhigear, an raon taghte. Chan eil na coraichean air an làn chleachdadh: dìreach mu 2/3 leis na ceithir snàithleanan sin.

Is e fuasgladh eile an seo a bhith ga chleachdadh a-mhàin Interlocked.CompareExchange leis an aon fheitheamh gnìomhach mar a tha air a shealltainn anns a 'chòd gu h-àrd (anns na feallsanachd acras), ach dh' fhaodadh seo, mar a chaidh a ràdh mar-thà, leantainn gu teòiridh bacadh.

air a ' Interlocked Bu chòir a thoirt fa-near nach eil ann a-mhàin CompareExchange, ach cuideachd dòighean eile airson leughadh AGUS sgrìobhadh atamach. Agus tro ath-aithris an atharrachaidh, air eagal ‘s gum bi ùine aig snàithlean eile na h-atharrachaidhean aige a dhèanamh (leugh 1, leugh 2, sgrìobh 2, sgrìobh 1 gu dona), faodar a chleachdadh airson atharrachaidhean iom-fhillte air aon luach (Pàtran Eadar-cheangailte Rud sam bith) .

Fuasglaidhean Modh Kernel

Gus nach caill sinn goireasan ann an lùb, chì sinn mar as urrainn dhuinn snàithlean a bhacadh. Ann am faclan eile, a 'leantainn ar n-eisimpleir, chì sinn mar a tha an neach-frithealaidh a' cur an fheallsanaiche gu cadal agus ga dhùsgadh dìreach nuair a bhios feum air. An toiseach, leig dhuinn sùil a thoirt air mar a nì thu seo tro mhodh kernel an t-siostaim obrachaidh. Tha a h-uile structar gu tric nas slaodaiche na an fheadhainn ann an àite luchd-cleachdaidh. Iomadh uair nas slaodaiche, mar eisimpleir AutoResetEvent is dòcha 53 tursan nas slaodaiche SpinLock [Richter]. Ach le an cuideachadh, faodaidh sibh sioncronaich pròiseasan air feadh an t-siostam, air a stiùireadh no nach eil.

Is e an togail bunaiteach an seo an semaphore a mhol Dijkstra o chionn leth-cheud bliadhna. Tha semaphore, dìreach air a chuir, na shàr-shluagh adhartach air a riaghladh leis an t-siostam, agus dà ghnìomhachd air, àrdachadh agus lùghdachadh. Ma dh’ fhailicheas e lùghdachadh, neoni, tha an snàthainn gairm air a bhacadh. Nuair a thèid an àireamh àrdachadh le snàithlean / pròiseas gnìomhach eile, thèid na snàithleanan a leumadh agus tha an semaphore a-rithist air a lughdachadh leis an àireamh a chaidh seachad. Faodaidh tu smaoineachadh air trèanaichean ann am botail le semaphore. Tha .NET a’ tabhann grunn chruthan leis an aon seòrsa gnìomh: AutoResetEvent, ManualResetEvent, Mutex agus mi-fhìn Semaphore. Cleachdaidh sinn AutoResetEvent, is e seo am fear as sìmplidh de na togalaichean sin: dìreach dà luach 0 agus 1 (meallta, fìor). A Modh WaitOne() a 'blocadh an t-snàthainn gairm ma bha an luach 0, agus ma tha 1, ga lùghdachadh gu 0 agus ga sgioblachadh. Dòigh-obrach Set() ag èirigh gu 1 agus a' leigeil le aon neach-frithealaidh a dhol troimhe, a tha a-rithist a' dol sìos gu 0. Ag obair mar cheum-tionndaidh fo-thalamh.

Dèanamaid iom-fhillte air an fhuasgladh agus cleachd a 'ghlas airson gach feallsanaiche, agus chan ann airson a h-uile duine aig an aon àm. An fheadhainn sin. a nis faodaidh iomadh feallsanach a bhi ann aig an aon àm, agus cha'n e a h-aon. Ach tha sinn a-rithist a 'cur bacadh air ruigsinneachd air a' bhòrd gus a bhith ceart, a 'seachnadh rèisean (suidheachadh rèis), gabh 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();
}

Gus tuigsinn dè a tha a 'tachairt an seo, beachdaich air a' chùis nuair nach do ghabh am feallsanaiche na forcaichean, agus bidh na gnìomhan aige mar a leanas. Tha e a’ feitheamh ri faighinn chun bhòrd. An dèidh dha fhaighinn, tha e a 'feuchainn ris na forcaichean a thoirt. Cha do dh'obraich a-mach. Tha e a 'toirt cothrom air a' bhòrd (a-mach às a chèile). agus a' dol seachad air a " cheum-tionndaidh" (AutoResetEvent) (tha iad fosgailte an toiseach). Bidh e a 'faighinn a-steach don chearcall a-rithist, oir chan eil forc aige. Bidh e a’ feuchainn rin toirt agus a’ stad aig an “turnstile” aige. Tha nàbaidh eigin a's sealbhaiche air an làimh dheis no chlì, air crìoch a chur air itheadh, a' fosgladh ar feallsanaiche, " a' fosgladh a cheuma." Bidh ar feallsanaiche a 'dol seachad air (agus tha e a' dùnadh air a chùlaibh) airson an dàrna uair. Bidh e a’ feuchainn airson an treas uair na forcaichean a thoirt leis. Beannachd leat. Agus bheir e seachad a cheum-tionndaidh gu dinneir.

Nuair a tha mearachdan air thuaiream ann an còd mar sin (bidh iad ann an-còmhnaidh), mar eisimpleir, tha nàbaidh air a shònrachadh gu ceàrr no thèid an aon rud a chruthachadh AutoResetEvent dha na h-uile (Enumerable.Repeat), an uairsin bidh na feallsanaich a’ feitheamh ris an luchd-leasachaidh, oir Is e obair gu math duilich a th’ ann a bhith a’ lorg mhearachdan ann an còd mar sin. Is e duilgheadas eile leis an fhuasgladh seo nach eil e a’ gealltainn nach bi an t-acras air cuid de fheallsanaiche.

Fuasglaidhean Hybrid

Tha sinn air sùil a thoirt air dà dhòigh-obrach a thaobh tìm, nuair a dh’fhuiricheas sinn ann am modh cleachdaiche agus lùb, agus nuair a chuireas sinn casg air snàithlean tron ​​kernel. Tha a 'chiad dhòigh math airson glasan goirid, an dàrna fear airson feadhainn fhada. Gu tric feumar feitheamh goirid an-toiseach airson caochladair atharrachadh ann an lùb, agus an uairsin casg a chuir air an t-snàthainn nuair a tha am feitheamh fada. Tha an dòigh-obrach seo air a bhuileachadh anns an t-ainm ris an canar. structaran tar-chinealach. Seo na h-aon structairean agus a tha airson modh kernel, ach a-nis le lùb modh cleachdaiche: SemaphorSlim, ManualResetEventSlim msaa Is e an dealbhadh as mòr-chòrdte an seo Monitor, oir ann an C# tha fear ainmeil lock co-chòrdadh. Monitor is e seo an aon semaphore le luach as àirde de 1 (mutex), ach le taic airson a bhith a’ feitheamh ann an lùb, ath-chuairteachadh, am pàtran Condition Variable (barrachd air sin gu h-ìosal), msaa Leigamaid sùil air fuasgladh leis.

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

An seo tha sinn a-rithist a’ bacadh a’ bhùird gu lèir airson faighinn gu na forcaichean, ach a-nis tha sinn a’ fuasgladh a h-uile snàithlean aig an aon àm, agus chan e nàbaidhean nuair a bhios cuideigin a’ crìochnachadh ithe. An fheadhainn sin. an toiseach, bidh cuideigin ag ithe agus a 'blocadh nan nàbaidhean, agus nuair a bhios an cuideigin seo a' crìochnachadh, ach ag iarraidh ithe a-rithist anns a 'bhad, bidh e a' dol a-steach gu bacadh agus a 'dùsgadh a nàbaidhean, oir. tha an ùine feitheimh aige nas lugha.

Seo mar a bhios sinn a’ seachnadh glasan-glasaidh agus acras cuid de fheallsanaiche. Cleachdaidh sinn lùb airson feitheamh goirid agus cuiridh sinn casg air an t-snàthainn airson fear fada. Tha a bhith a’ fuasgladh a h-uile duine aig an aon àm nas slaodaiche na nam biodh an nàbaidh air a bhacadh a-mhàin, mar a tha san fhuasgladh leis AutoResetEvent, ach cha bu chòir an eadar-dhealachadh a bhith mòr, oir feumaidh snàithleanan fuireach ann am modh cleachdaiche an toiseach.

У lock tha iongantasan mì-nàdurrach aig syntax. Moladh a chleachdadh Monitor gu dìreach [Richter] [Eric Lippert]. Is e aon dhiubh sin lock an-còmhnaidh a-mach à Monitor, eadhon ged a bhiodh eisgeachd ann, agus mar sin dh’ fhaodadh snàithlean eile an staid cuimhne co-roinnte atharrachadh. Ann an leithid de chùisean, gu tric tha e nas fheàrr a dhol gu deadlock no dòigh air choireigin crìoch a chuir air a ’phrògram. Is e iongnadh eile a th’ ann gu bheil Monitor a’ cleachdadh blocaichean sioncronaidh (SyncBlock), a tha làthair anns na h-uile nithibh. Mar sin, ma thèid rud neo-iomchaidh a thaghadh, faodaidh tu stad-stad fhaighinn gu furasta (mar eisimpleir, ma ghlasas tu air sreang a-staigh). Cleachdaidh sinn an rud a tha daonnan falaichte airson seo.

Leigidh am pàtran Condition Variable leat an dùil ri suidheachadh iom-fhillte a chuir an gnìomh nas mionaidiche. Ann an .NET, tha e neo-choileanta, nam bheachd-sa, oir ann an teòiridh, bu chòir grunn ciudhaichean a bhith ann air grunn chaochladairean (mar ann am Posix Threads), agus chan ann air aon lok. An uairsin dh'fhaodadh aon a dhèanamh airson a h-uile feallsanachd. Ach eadhon anns an fhoirm seo, leigidh e leat an còd a lughdachadh.

iomadh feallsanaiche no async / await

Ceart gu leòr, a-nis is urrainn dhuinn snàithleanan a bhacadh gu h-èifeachdach. Ach dè ma tha tòrr fheallsanaich againn? 100? 10000? Mar eisimpleir, fhuair sinn 100000 iarrtas chun an fhrithealaiche lìn. Bidh e os cionn snàithlean a chruthachadh airson gach iarrtas, oir cha bhi uiread de snàithleanan a 'ruith ann an co-shìnte. Cha ruith e ach na h-uimhir ’s a tha ann an coraichean loidsigeach (tha 4 agam). Agus cha toir a h-uile duine eile ach goireasan air falbh. Is e aon fhuasgladh don duilgheadas seo am pàtran async / await. Is e a bheachd nach eil an gnìomh a’ cumail an t-snàthainn ma dh’ fheumas e feitheamh gus an lean rudeigin. Agus nuair a nì e rudeigin, bidh e ag ath-thòiseachadh a chur gu bàs (ach chan ann gu riatanach air an aon snàithlean!). Anns a 'chùis againn, bidh sinn a' feitheamh ris a 'ghobhar.

SemaphoreSlim tha aige airson seo WaitAsync() dòigh-obrach. Seo buileachadh a’ cleachdadh a’ phàtrain seo.

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

Dòigh-obrach le async / await air eadar-theangachadh gu inneal stàite duilich a thilleas sa bhad a-staigh Task. Troimhe, faodaidh tu feitheamh airson an dòigh a chrìochnachadh, a chuir dheth, agus a h-uile càil eile as urrainn dhut a dhèanamh le Task. Taobh a-staigh a 'mhodh, tha an inneal stàite a' cumail smachd air coileanadh. Is e an loidhne gu h-ìosal mura h-eil dàil ann, gu bheil an coileanadh sioncronaich, agus ma tha, thèid an snàithlean a leigeil ma sgaoil. Airson tuigse nas fheàrr air seo, tha e nas fheàrr coimhead air an inneal stàite seo. Faodaidh tu slabhraidhean a chruthachadh dhiubh sin async / await dòighean-obrach.

Dèanamaid deuchainn. Obair 100 feallsanaiche air inneal le 4 coraichean loidsigeach, 8 diogan. Cha do ruith am fuasgladh roimhe le Monitor ach a’ chiad 4 snàithleanan agus cha do ruith an còrr idir. Bha gach aon de na 4 snàithleanan sin leisg airson timcheall air 2ms. Agus ruith am fuasgladh async / await air fad 100, le feitheamh cuibheasach de 6.8 diogan gach fear. Gu dearbh, ann an siostaman fìor, chan eil e ceadaichte a bhith leisg airson 6 diogan agus tha e nas fheàrr gun a bhith a ’giullachd uimhir de dh’ iarrtasan mar seo. Cha robh am fuasgladh le Monitor scalable idir.

co-dhùnadh

Mar a chì thu bho na h-eisimpleirean beaga seo, tha .NET a' toirt taic do dh'iomadh cruth sioncronaidh. Ach, chan eil e an-còmhnaidh follaiseach mar a chleachdas iad iad. Tha mi an dòchas gun robh an artaigil seo cuideachail. Airson a-nis, is e seo an deireadh, ach tha tòrr rudan inntinneach air fhàgail fhathast, mar eisimpleir, cruinneachaidhean sàbhailte snàithlean, TPL Dataflow, prògramadh ath-ghnìomhach, modail bathar-bog malairt, msaa.

Stòran

Source: www.habr.com

Cuir beachd ann