Fealsúna dea-chothaithe nó Ríomhchlárú Iomaíoch

Fealsúna dea-chothaithe nó Ríomhchlárú Iomaíoch

Féachaimis ar an gcaoi a n-oibríonn ríomhchlárú comhthráthach agus comhthreomhar in .Net, ag baint úsáide as Fadhb Itheacháin na bhFealsúnaí mar shampla. Is é seo an plean, ó shioncronú snáitheanna / próiseas, go múnla an aisteoir (sna codanna seo a leanas). Féadfaidh an t-alt a bheith úsáideach don chéad aithne nó chun do chuid eolais a athnuachan.

Cén fáth é a dhéanamh ar chor ar bith? Sroicheann trasraitheoirí a n-íosmhéid, luíonn dlí Moore ar theorannú luas an tsolais agus mar sin breathnaítear méadú ar an líon, is féidir níos mó trasraitheoirí a dhéanamh. Ag an am céanna, tá an méid sonraí ag fás, agus úsáideoirí ag súil le freagra láithreach ó na córais. I gcás den sórt sin, ní bhíonn "gnáthchlárú" éifeachtach a thuilleadh nuair a bhíonn snáithe forghníomhaithe amháin againn. Ní mór duit an fhadhb a bhaineann le forghníomhú comhuaineach nó comhthráthach a réiteach ar bhealach éigin. Thairis sin, tá an fhadhb seo ann ar leibhéil éagsúla: ar leibhéal na snáitheanna, ar leibhéal na bpróiseas, ar leibhéal na meaisíní sa líonra (córais dáilte). Tá teicneolaíochtaí ar ardchaighdeán, a bhfuil tástáil ama orthu, ag NET chun fadhbanna den sórt sin a réiteach go tapa agus go héifeachtach.

Tasc

Chuir Edsger Dijkstra an fhadhb seo ar a chuid mac léinn chomh luath le 1965. Seo a leanas an fhoirmiú bunaithe. Tá líon áirithe (de ghnáth cúig) fealsúna agus an líon céanna forcanna. Suíonn siad ag bord cruinn, forcanna eatarthu. Is féidir le fealsúna ithe as a gcuid plátaí bia gan deireadh, smaoineamh nó fanacht. Chun fealsamh a ithe, ní mór duit dhá fhorc a ghlacadh (roinneann an ceann deireanach an forc leis an gcéad cheann). Is dhá ghníomh ar leith iad forc a phiocadh suas agus a chur síos. Tá gach fealsamh ina dtost. Is é an tasc ná algartam den sórt sin a aimsiú a cheapfadh gach duine acu agus go mbeadh sé iomlán fiú tar éis 54 bliain.

Ar dtús, déanaimis iarracht an fhadhb seo a réiteach trí úsáid a bhaint as spás comhroinnte. Luíonn na forcanna ar an mbord coiteann agus ní thógann na fealsúna ach iad nuair a bhíonn siad agus cuireann siad ar ais iad. Anseo tá fadhbanna le sioncrónú, cathain go díreach surebets a ghlacadh? cad muna bhfuil forc ann? etc Ach ar dtús, cuirimis tús leis na fealsúna.

Chun snáitheanna a thosú, úsáidimid linn snáithe tríd Task.Run modh:

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

Tá an linn snáithe deartha chun cruthú agus scriosadh snáithe a bharrfheabhsú. Tá scuaine ag an linn seo le tascanna agus cruthaíonn nó baintear snáitheanna ag an CLR ag brath ar líon na dtascanna seo. Linn snámha amháin do gach AppDomains. Ba chóir an linn snámha seo a úsáid beagnach i gcónaí, mar gheall ar. ní gá bac a chur le cruthú, scriosadh snáitheanna, a gcuid scuainí, etc. Is féidir gan linn snámha, ach ansin caithfidh tú é a úsáid go díreach Thread, tá sé seo úsáideach le haghaidh cásanna nuair is gá duit tosaíocht snáithe a athrú, nuair a bhíonn oibríocht fhada againn, le haghaidh snáithe Foreground, etc.

I bhfocail eile, System.Threading.Tasks.Task rang mar an gcéanna Thread, ach le gach cineál áiseanna: an cumas tasc a rith tar éis bloc tascanna eile, iad a chur ar ais ó fheidhmeanna, cur isteach orthu go caothúil, agus níos mó. etc. Tá siad ag teastáil chun tacú le tógálacha async / fanacht (Patrún Asincrónach Tascbhunaithe, siúcra comhréire le haghaidh fanacht le hoibríochtaí IO). Labhróimid níos mó faoi seo.

CancelationTokenSource anseo tá sé ag teastáil ionas gur féidir leis an snáithe é féin a fhoirceannadh ag comhartha na snáithe ag glaoch.

Saincheisteanna Sioncronaithe

Fealsúna bactha

Ceart go leor, tá a fhios againn conas snáitheanna a chruthú, déanaimis iarracht lón a bheith agat:

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

Anseo déanaimid iarracht an forc clé a ghlacadh ar dtús, agus ansin an forc ceart, agus má oibríonn sé amach, ansin ithimid agus cuirimid ar ais iad. Tá glacadh forc amháin adamhach, i.e. ní féidir le dhá shnáithe ceann a ghlacadh ag an am céanna (mícheart: léann an chéad cheann go bhfuil an forc saor, an dara - freisin, glacann an chéad cheann, glacann an dara ceann). Dó seo Interlocked.CompareExchange, a chaithfear a chur i bhfeidhm le treoir próiseálaí (TSL, XCHG), a ghlasann píosa cuimhne don léitheoireacht agus don scríobh seicheamhach adamhach. Agus is ionann SpinWait agus an tógáil while(true) ach le beagán "draíochta" - glacann an snáithe an próiseálaí (Thread.SpinWait), ach uaireanta aistríonn sé rialú go snáithe eile (Thread.Yeild) nó go dtiteann sé ina chodladh (Thread.Sleep).

Ach ní oibríonn an réiteach seo, mar gheall ar cuirtear bac ar na sreafaí go luath (domsa laistigh de soicind): glacann gach fealsamh a forc clé, ach ní hé an ceann ceart. Tá na luachanna ansin ag eagar na bhforc: 1 2 3 4 5.

Fealsúna dea-chothaithe nó Ríomhchlárú Iomaíoch

Sa fhigiúr, blocáil snáitheanna (deadlock). Glas - forghníomhú, dearg - sioncrónú, liath - tá an snáithe codlata. Léiríonn na rhombuses am tosaithe na dTasc.

Ocras na bhFealsúnaí

Cé nach bhfuil sé riachtanach chun smaoineamh go háirithe bia i bhfad, ach ocras a dhéanann duine ar bith a thabhairt suas fealsúnacht. Déanaimis iarracht insamhail a dhéanamh ar staid ocras snáitheanna inár bhfadhb. Is é an t-ocras nuair a bhíonn snáithe ag rith, ach gan obair shuntasach, i bhfocail eile, is é seo an tsáinn chéanna, ach anois níl an snáithe codlata, ach tá sé ag lorg go gníomhach le haghaidh rud éigin le hithe, ach níl aon bhia ann. D'fhonn blocáil go minic a sheachaint, cuirfimid an forc ar ais mura bhféadfaimis ceann eile a ghlacadh.

// То же что и в 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 é an rud is tábhachtaí faoin gcód seo ná go ndéanann beirt as gach ceathrar fealsamh dearmad ar a bhforc clé a chur síos. Agus tharlaíonn sé go n-itheann siad níos mó bia, agus tosaíonn daoine eile ag ocras, cé go bhfuil an tosaíocht chéanna ag na snáitheanna. Anseo nach bhfuil siad go hiomlán starving, mar gheall ar. cuireann fealsúna olc a gcuid forcanna ar ais uaireanta. Tarlaíonn sé go n-itheann daoine maithe thart ar 5 huaire níos lú ná na cinn dona. Mar sin tagann laghdú ar fheidhmíocht mar thoradh ar earráid bheag sa chód. Is fiú a thabhairt faoi deara anseo freisin go bhfuil staid annamh indéanta nuair a thógann gach fealsamh an forc clé, níl aon cheann ceart ann, cuireann siad an ceann clé, fan, tóg an ceann clé arís, etc. Is ocras an scéal seo freisin, níos cosúla le tsáinn. Theip orm é a dhéanamh arís. Seo thíos pictiúr do chás inar ghlac beirt drochfhealsamh an dá fhorc agus ina bhfuil dhá fhealsamh maith ag ocras.

Fealsúna dea-chothaithe nó Ríomhchlárú Iomaíoch

Anseo is féidir leat a fheiceáil go dúisíonn na snáitheanna uaireanta agus iarracht a dhéanamh an acmhainn a fháil. Ní dhéanann dhá cheann de na ceithre chroílár faic (graf glas thuas).

Bás Fealsúnaí

Bhuel, fadhb eile a chuireann isteach ar dhinnéar glórmhar fealsúna ná má fhaigheann duine acu bás go tobann le forcanna ina lámha (agus adhlacfaidh siad é mar sin). Ansin fágfar na comharsana gan lón. Is féidir leat teacht suas le cód sampla don chás seo tú féin, mar shampla, tá sé thrown amach NullReferenceException tar éis don fhealsamh na forcanna a ghlacadh. Agus, dála an scéil, ní bheidh an eisceacht a láimhseáil agus ní bheidh an cód glaoch a ghabháil ach é (do seo AppDomain.CurrentDomain.UnhandledException agus araile). Dá bhrí sin, tá gá le láimhseálaithe earráide sna snáitheanna féin agus le foirceannadh galánta.

Waiter

Ceart go leor, conas a réitímid an tsáinn seo, an t-ocras agus an bás? Tabharfaimid cead d'fhealsamh amháin na forcanna a bhaint amach, cuir eisiamh frithpháirteach snáitheanna don áit seo. Conas é a dhéanamh? Cuir i gcás go bhfuil freastalaí ina sheasamh in aice leis na fealsúna, a thugann cead d'aon fhealsamh amháin na forcanna a ghlacadh. Conas a dhéanfaimid an freastalaí seo agus conas a chuirfidh fealsúna air, tá na ceisteanna suimiúil.

Is é an bealach is simplí nuair a iarrfaidh fealsúna go simplí ar an bhfreastalaí rochtain a fháil ar na forcanna. Iad siúd. anois ní bheidh fealsúna ag fanacht le forc in aice láimhe, ach fanacht nó a iarraidh ar an waiter. Ar dtús, ní úsáidimid ach Spás Úsáideora chuige seo, ní bhainimid úsáid as idirbhriseadh chun aon nósanna imeachta a ghlaoch ón eithne (mar gheall orthu thíos).

Réitigh sa spás úsáideora

Anseo déanfaimid an rud céanna mar a rinneamar le forc amháin agus dhá fhealsamh, déanfaimid casadh i dtimthriall agus fanfaimid. Ach anois beidh sé ina fhealsamh ar fad agus, mar a bhí, gan ach forc amháin, i.e. is féidir a rá nach n-íosfaidh ach an fealsamh a ghlac an “forc órga” seo ón bhfreastalaí. Chun seo a úsáid againn 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 tá sé seo ina bhac, le, go garbh, mar an gcéanna while(true) { if (!lock) break; }, ach le fiú níos mó "draíochta" ná i SpinWait (a úsáidtear ann). Anois tá a fhios aige conas iad siúd atá ag fanacht a chomhaireamh, iad a chur a chodladh beagán, agus níos mó. etc Go ginearálta, a dhéanann gach rud is féidir a bharrfheabhsú. Ach ní mór dúinn cuimhneamh gurb é seo fós an timthriall gníomhach céanna a itheann acmhainní próiseálaí agus a choimeádann an sreabhadh, rud a d'fhéadfadh ocras a bheith mar thoradh air má éiríonn ceann de na fealsúna níos mó tosaíochta ná a chéile, ach nach bhfuil forc órga aige (fadhb inbhéartaithe tosaíochta) . Dá bhrí sin, ní úsáidimid é ach le haghaidh athruithe an-ghearr ar chuimhne roinnte, gan aon ghlaonna tríú páirtí, glais neadaithe, agus iontas eile.

Fealsúna dea-chothaithe nó Ríomhchlárú Iomaíoch

Ag tarraingt le haghaidh SpinLock. Tá na sruthanna i gcónaí "troid" don forc órga. Tá teipeanna ann - san fhigiúr, an limistéar roghnaithe. Ní bhaintear úsáid iomlán as na croíleacáin: níl ach thart ar 2/3 ag na ceithre snáithe seo.

Réiteach eile a bheadh ​​anseo ná úsáid amháin Interlocked.CompareExchange leis an fanacht gníomhach céanna mar a léirítear sa chód thuas (sna fealsúna ocras), ach d'fhéadfadh sé seo, mar a dúradh cheana, bac a chur go teoiriciúil.

Про Interlocked Ba chóir a thabhairt faoi deara nach bhfuil ach CompareExchange, ach freisin modhanna eile chun léamh AGUS scríobh adamhach. Agus tríd an athrú a athrá, ar eagla go mbeidh am ag snáithe eile a chuid athruithe a dhéanamh (léigh 1, léigh 2, scríobh 2, scríobh 1 go dona), is féidir é a úsáid le haghaidh athruithe casta ar luach amháin (patrún Idirghlasáilte Rud ar bith) .

Réitigh Mód Eithne

Chun cur amú acmhainní i lúb a sheachaint, féachaimis conas is féidir linn snáithe a bhlocáil. I bhfocail eile, ag leanúint lenár sampla, féachaimis conas a chuireann an freastalaí an fealsamh a chodladh agus a dhúisíonn sé ach amháin nuair is gá. Ar dtús, déanaimis féachaint ar conas é seo a dhéanamh trí mhodh eithne an chórais oibriúcháin. Is minic a bhíonn gach struchtúr níos moille ná iad siúd atá i spás úsáideora. Roinnt uaireanta níos moille, mar shampla AutoResetEvent b'fhéidir 53 uair níos moille SpinLock [Richter]. Ach lena gcabhair, is féidir leat próisis a shioncrónú ar fud an chórais, a bhainistiú nó nach bhfuil.

Is é an tógáil bhunúsach anseo ná an leathchéad bliain ó shin a mhol Dijkstra. Is éard atá i semaphore, go simplí, slánuimhir dhearfach arna bhainistiú ag an gcóras, agus dhá oibríocht air, incrimint agus laghdú. Má theipeann air a laghdú, náid, ansin tá an snáithe glaoch bac. Nuair a mhéadaítear an uimhir le snáithe/próiseas gníomhach eile, ansin déantar na snáitheanna a scipeáil agus déantar an semaphore a laghdú arís leis an uimhir a ritheadh. Is féidir traenacha a shamhlú i tranglam le semaphore. Cuireann .NET roinnt tógálacha ar fáil a bhfuil feidhmiúlacht chomhchosúil acu: AutoResetEvent, ManualResetEvent, Mutex agus mé féin Semaphore. Úsáidfimid AutoResetEvent, is é seo an ceann is simplí de na tógálacha seo: ach dhá luach 0 agus 1 (bréagach, fíor). A Modh WaitOne() bloic an snáithe glaonna dá mba é an luach ná 0, agus má íslíonn 1 é, íslíonn sé go 0 é agus seachnaíonn sé é. Modh Set() ardaíonn sé go dtí 1 agus ligeann freastalaí amháin tríd, a íslíonn arís go 0. Gníomhaíonn cosúil le casstile fobhealach.

Déanaimis an réiteach níos casta agus bain úsáid as an glas do gach fealsamh, agus ní do gach duine ag an am céanna. Iad siúd. anois is féidir go mbeadh roinnt fealsúna ag an am céanna, agus ní amháin. Ach táimid bloc arís rochtain ar an tábla d'fhonn a gceart, rásaí a sheachaint (coinníollacha cine), surebets a ghlacadh.

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

Chun a thuiscint cad atá ag tarlú anseo, breithnigh an cás nuair a theip ar an fealsamh na forcanna a ghlacadh, ansin beidh a chuid gníomhartha mar seo a leanas. Tá sé ag fanacht le rochtain ar an mbord. Tar éis dó é a fháil, déanann sé iarracht na forcanna a thógáil. Níor oibrigh amach. Tugann sé rochtain ar an tábla (eisiamh frithpháirteach). Agus téann sé thar a "casadh" (AutoResetEvent) (tá siad oscailte ar dtús). Faigheann sé isteach sa timthriall arís, mar gheall ar níl aon fhorcanna aige. Déanann sé iarracht iad a thógáil agus stopann sé ag a "chastán cas". Roinnt comharsa níos ádh ar dheis nó ar chlé, tar éis críochnú ithe, unlocks ár fealsamh, "a oscailt a turnstile." Gabhann ár bhfealsamh é (agus dúnann sé taobh thiar de) don dara huair. Déanann sé iarracht den tríú huair na forcanna a thógáil. Ádh mór. Agus téann sé thar a casstile chun dinnéir.

Nuair a bhíonn earráidí randamacha sa chód sin (bíonn siad ann i gcónaí), mar shampla, sonraítear comharsa go mícheart nó cruthaítear an réad céanna AutoResetEvent do chách (Enumerable.Repeat), ansin beidh na fealsúna ag fanacht leis na forbróirí, mar gheall ar Is tasc deacair go leor é earráidí a aimsiú i gcód den sórt sin. Fadhb eile leis an réiteach seo ná nach ráthaíonn sé nach mbeidh ocras ar fhealsamh éigin.

Réitigh Hibrid

D'fhéachamar ar dhá chur chuige maidir le huainiú, nuair a fhanaimid i mód úsáideora agus lúb, agus nuair a dhéanaimid bac ar snáithe tríd an eithne. Tá an chéad mhodh maith le haghaidh glais ghearr, an dara ceann le haghaidh cinn fada. Is minic is gá fanacht go hachomair ar dtús le haghaidh athróg a athrú i lúb, agus ansin an snáithe a bhlocáil nuair a bhíonn an fanacht fada. Cuirtear an cur chuige seo i bhfeidhm mar a thugtar air. struchtúir hibrideacha. Seo iad na tógálacha céanna is atá le modh eithne, ach anois le lúb mód úsáideora: SemaphorSlim, ManualResetEventSlim srl. Tá an dearadh is coitianta anseo Monitor, mar in C# tá clú agus cáil lock comhréir. Monitor is é seo an semaphore céanna le luach uasta de 1 (mutex), ach le tacaíocht le haghaidh fanacht i lúb, atarlú, an patrún Athróg Coinníoll (níos mó ar sin thíos), etc A ligean ar breathnú ar réiteach 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);
    }
}

Anseo táimid ag blocáil an tábla ar fad arís le haghaidh rochtana ar na forcanna, ach anois táimid ag díbhlocáil na snáitheanna go léir ag an am céanna, agus ní comharsana nuair a chríochnaíonn duine ag ithe. Iad siúd. ar dtús, itheann duine agus bloic na comharsana, agus nuair a chríochnaíonn an duine seo, ach ba mhaith leis a ithe arís ar an bpointe boise, téann sé isteach blocáil agus dúisíonn sé a chomharsana, mar gheall ar. tá a chuid ama feithimh níos lú.

Seo é an chaoi a seachnaímid sladmhargadh agus ocras fealsamh éigin. Bainimid úsáid as lúb ar feadh fanacht gearr agus bloc an snáithe ar feadh ceann fada. Tá sé níos moille an bac a chur ar gach duine ag an am céanna ná dá mba rud é nár baineadh bac ach leis an gcomharsa, mar atá sa réiteach AutoResetEvent, ach níor chóir go mbeadh an difríocht mhór, mar gheall ar caithfidh snáitheanna fanacht i mód úsáideora ar dtús.

У lock Tá iontas olc ar chomhréir. Mholadh a úsáid Monitor go díreach [Richter] [Eric Lippert]. Is é ceann acu sin lock i gcónaí as Monitor, fiú dá mbeadh eisceacht ann, agus sa chás sin d'fhéadfadh snáithe eile an staid chuimhne roinnte a athrú. I gcásanna den sórt sin, is minic a bhíonn sé níos fearr dul go dtí an tsáinn nó an clár a fhoirceannadh ar bhealach sábháilte. Ábhar iontais eile is ea go n-úsáideann Monitor bloic sioncrónaithe (SyncBlock), atá i láthair i ngach réad. Dá bhrí sin, má roghnaítear rud míchuí, is féidir leat sáinneáil a fháil go héasca (mar shampla, má ghlasann tú ar theaghrán imtheorannaithe). Bainimid úsáid as an réad i gcónaí i bhfolach le haghaidh seo.

Ligeann an patrún Coinníoll Athraitheach duit an t-ionchas le riocht casta éigin a chur i bhfeidhm ar bhealach níos gonta. I .NET, tá sé neamhiomlán, i mo thuairim, mar gheall ar go teoiriciúil, ba cheart go mbeadh roinnt scuainí ar roinnt athróg (mar atá i Snáitheanna Posix), agus ní ar aon áit amháin. Ansin d'fhéadfadh duine iad a dhéanamh do gach fealsúna. Ach fiú san fhoirm seo, ligeann sé duit an cód a laghdú.

go leor fealsamh nó async / await

Ceart go leor, anois is féidir linn snáitheanna a bhlocáil go héifeachtach. Ach cad má tá go leor fealsúna againn? 100? 10000? Mar shampla, fuaireamar 100000 iarratas chuig an bhfreastalaí gréasáin. Beidh sé forchostais snáithe a chruthú do gach iarratas, mar gheall ar ní bheidh an oiread sin snáitheanna ag rith go comhthreomhar. Ní reáchtálfar ach an oiread agus a bhfuil croíleacáin loighciúil ann (tá 4 agam). Agus beidh gach duine eile a chur amach ach acmhainní. Réiteach amháin ar an bhfadhb seo is ea an patrún async / await. Is é an smaoineamh atá aige ná nach gcoimeádann an fheidhm an snáithe más gá dó fanacht le leanúint ar aghaidh le rud éigin. Agus nuair a dhéanann sé rud éigin, athuair sé a fhorghníomhú (ach ní gá ar an snáithe céanna!). In ár gcás, fanfaimid an forc.

SemaphoreSlim Tá le haghaidh seo WaitAsync() modh. Seo cur i bhfeidhm ag baint úsáide as an bpatrún 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();
}

Modh le async / await aistrítear go meaisín stát tricky a chuireann a inmheánach ar ais láithreach Task. Tríd é, is féidir leat fanacht le críochnú an mhodha, é a chealú, agus gach rud eile is féidir leat a dhéanamh le Tasc. Taobh istigh den mhodh, rialaíonn an meaisín stáit an forghníomhú. Is é an bunlíne ná mura bhfuil aon mhoill ann, ansin tá an forghníomhú sioncrónach, agus má tá, ansin scaoiltear an snáithe. Chun tuiscint níos fearr a fháil ar seo, is fearr breathnú ar an meaisín stáit seo. Is féidir leat slabhraí a chruthú uathu seo async / await modhanna.

Déanaimis tástáil. Obair 100 fealsamh ar mheaisín le 4 chroílár loighciúil, 8 soicind. Níor rith an réiteach roimhe seo le Monitor ach na chéad 4 snáithe agus níor rith an chuid eile ar chor ar bith. Bhí gach ceann de na 4 snáitheanna díomhaoin ar feadh thart ar 2ms. Agus rith an réiteach async / await gach 100, le meán fanacht de 6.8 soicind an ceann. Ar ndóigh, i bhfíorchórais, tá díomhaoin ar feadh 6 soicind do-ghlactha agus is fearr gan an oiread sin iarratas mar seo a phróiseáil. Ní raibh an réiteach le Monitor inscálaithe ar chor ar bith.

Conclúid

Mar a fheiceann tú ó na samplaí beaga seo, tacaíonn .NET le go leor tógálacha sioncronaithe. Mar sin féin, ní léir i gcónaí conas iad a úsáid. Tá súil agam go raibh an t-alt seo cabhrach. Go dtí seo, is é seo an deireadh, ach tá a lán rudaí suimiúla fágtha fós, mar shampla, bailiúcháin sábháilte snáithe, TPL Dataflow, ríomhchlárú Imoibríoch, samhail Idirbheart Bogearraí, etc.

Foinsí

Foinse: will.com

Add a comment