ښه فیډ فیلسوفان یا رقابتي .NET پروګرامونه

ښه فیډ فیلسوفان یا رقابتي .NET پروګرامونه

راځئ وګورو چې په .Net کې همغږي او موازي پروګرامونه څنګه کار کوي، د مثال په توګه د فیلسوفانو د خواړو ستونزه کارول. پلان دا دی ، د تارونو / پروسو له ترکیب څخه د اداکار ماډل ته (په لاندې برخو کې). مقاله ممکن د لومړي آشنا لپاره یا ستاسو د پوهې تازه کولو لپاره ګټوره وي.

ولې دا په بشپړه توګه کوئ؟ ټرانزیسټرونه خپل لږ تر لږه اندازې ته رسیږي، د مور قانون د رڼا د سرعت په محدودیت پورې اړه لري او له همدې امله په شمیر کې زیاتوالی لیدل کیږي، ډیر ټرانزیسټرونه رامینځته کیدی شي. په ورته وخت کې، د معلوماتو اندازه وده کوي، او کاروونکي د سیسټمونو څخه د سمدستي غبرګون تمه لري. په داسې حالت کې، "نورمال" برنامه کول، کله چې موږ یو اجرا کوونکی تار لرو، نور اغیزمن ندي. تاسو اړتیا لرئ په یو ډول سره د یوځل یا همغږي اجرا کولو ستونزه حل کړئ. سربیره پردې، دا ستونزه په مختلفو کچو کې شتون لري: د تارونو په کچه، د پروسو په کچه، په شبکه کې د ماشینونو په کچه (توزیع شوي سیسټمونه). .NET د دې ډول ستونزو په چټکه او اغیزمنه توګه حل کولو لپاره لوړ کیفیت لرونکي، وخت ازمول شوي ټیکنالوژي لري.

موخه

Edsger Dijkstra دا ستونزه د 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دا د هغو قضیو لپاره ګټور دی کله چې تاسو اړتیا لرئ د تار لومړیتوب بدل کړئ، کله چې موږ اوږد عملیات لرو، د مخکینۍ موضوع لپاره، او داسې نور.

په نورو ټکو ، System.Threading.Tasks.Task ټولګی یو شان دی Thread، مګر د هر ډول اسانتیاو سره: د نورو دندو بلاک وروسته د دندې پرمخ وړلو وړتیا ، له دندو څخه یې بیرته راګرځول ، په اسانۍ سره یې مداخله کول او نور ډیر څه. etc. دوی د async / انتظار جوړونې مالتړ لپاره اړین دي (د کار پراساس اسینکرونوس نمونه ، د 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 او داسې نور). له همدې امله، د تېروتنې سمبالونکي پخپله په تارونو کې او په زړه پورې پای ته رسولو ته اړتیا لري.

ویټر

ښه، موږ څنګه د دې بند، لوږې او مرګ ستونزه حل کړو؟ موږ به یوازې یو فیلسوف ته اجازه ورکړو چې فورک ته ورسیږو، د دې ځای لپاره د تارونو دوه اړخیز اخراج اضافه کړو. دا څنګه وکړو؟ فرض کړئ چې د فیلسوفانو تر څنګ یو انتظار کوونکی ولاړ دی، څوک چې کوم یو فیلسوف ته د فورکس اخیستلو اجازه ورکوي. دا ویټر څنګه جوړ کړو او څنګه به فیلسوفان ترې پوښتنه وکړي، پوښتنې په زړه پورې دي.

ترټولو ساده لاره دا ده چې فیلسوفان به په دوامداره توګه د ویټر څخه فورکس ته د لاسرسي غوښتنه وکړي. هغوی. اوس به فیلسوفان نږدې د پوټکي انتظار نه کوي، مګر انتظار کوي یا د انتظار څخه پوښتنه کوي. په لومړي سر کې، موږ د دې لپاره یوازې د کاروونکي ځای کاروو، په دې کې موږ د کرنل څخه د کومې پروسیجرونو غږولو لپاره مداخلې نه کاروو (د دوی په اړه لاندې).

د کاروونکي ځای کې حلونه

دلته به هماغسې کوو لکه د یوه کانګې او دوه فیلسوفانو سره به مو په یوه څرخ کې چکر وهو او انتظار به کوو. مګر اوس به دا ټول فیلسوفان وي او لکه څنګه چې وو، یوازې یو فورک، یعنی. دا ویل کیدی شي چې یوازې هغه فیلسوف چې دا "طلایی فورک" یې له ویټر څخه اخیستی وخوري. د دې لپاره موږ 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 (چې هلته کارول کیږي). اوس هغه پوهیږي چې څنګه د انتظار خلکو شمیره وکړي، لږ خوب ته یې واچوي، او نور ډیر څه. او داسې نور. مګر موږ باید په یاد ولرو چې دا لاهم هماغه فعال جریان دی چې د پروسیسر سرچینې خوري او جریان ساتي ، کوم چې د لوږې لامل کیدی شي که چیرې یو فیلسوف د نورو په پرتله ډیر لومړیتوب ولري ، مګر د سرو زرو فورک نلري (د لومړیتوب انډول ستونزه) . له همدې امله، موږ دا یوازې په شریکه حافظه کې د خورا لنډ بدلونونو لپاره کاروو، پرته له دې چې د دریمې ډلې زنګونو، نیست شوي لاکونو، او نورو حیرانتیاو څخه.

ښه فیډ فیلسوفان یا رقابتي .NET پروګرامونه

لپاره رسم کول SpinLock. جریان په دوامداره توګه د سرو زرو لپاره "جګړه" کوي. ناکامۍ شتون لري - په انځور کې، ټاکل شوې ساحه. کورونه په بشپړه توګه ندي کارول شوي: یوازې د دې څلورو تارونو لخوا شاوخوا 2/3.

دلته بل حل به یوازې د کارولو لپاره وي Interlocked.CompareExchange د ورته فعال انتظار سره لکه څنګه چې په پورته کوډ کې ښودل شوي (په لوږه اخته فیلسوفانو کې)، مګر دا، لکه څنګه چې مخکې ویل شوي، په نظرياتي توګه د بندیدو لامل کیدی شي.

په Interlocked دا باید په پام کې ونیول شي چې یوازې شتون نلري CompareExchange، مګر د اټومي لوستلو او لیکلو لپاره نورې میتودونه هم. او د بدلون د تکرار له لارې، په هغه صورت کې چې بله موضوع د خپلو بدلونونو لپاره وخت ولري (لوستل 1، لوستل 2، لیکل 2، لیکل 1 خراب دی)، دا د یو واحد ارزښت لپاره د پیچلو بدلونونو لپاره کارول کیدی شي (Interlocked Anything pattern) .

د کرنل موډ حلونه

د دې لپاره چې په لوپ کې د سرچینو ضایع کیدو مخه ونیسو، راځئ وګورو چې موږ څنګه یو تار بندولی شو. په بل عبارت، زموږ د مثال په ادامه کې، راځئ چې وګورو چې څنګه ویټر فیلسوف خوب ته اړوي او یوازې د اړتیا په وخت کې یې بیداروي. لومړی، راځئ وګورو چې دا څنګه د عملیاتي سیسټم د کرنل حالت له لارې ترسره کیږي. هلته ټول جوړښتونه اکثرا د کارونکي ځای په پرتله ورو وي. څو ځله ورو، د مثال په توګه 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، حتی که چیرې یو استثنا شتون ولري ، په کوم حالت کې بل تار کولی شي د ګډ حافظې حالت بدل کړي. په داسې قضیو کې ، دا ډیری وختونه غوره دي چې ځنډ ته لاړشئ یا په یو ډول په خوندي ډول برنامه فسخه کړئ. بل حیرانتیا دا ده چې مانیټر د همغږي کولو بلاکونه کاروي (SyncBlock)، کوم چې په ټولو شیانو کې شتون لري. نو له همدې امله، که یو نامناسب څیز غوره شوی وي، تاسو کولی شئ په اسانۍ سره یو خنډ ترلاسه کړئ (د مثال په توګه، که تاسو په داخلي تار کې بند کړئ). موږ د دې لپاره تل پټ توکي کاروو.

د حالت متغیر نمونه تاسو ته اجازه درکوي په ډیر لنډ ډول د ځینې پیچلي حالت تمه پلي کړئ. په .NET کې، دا نیمګړی دی، زما په نظر، ځکه په تیوري کې، باید په څو متغیرونو کې څو کتارونه وي (لکه څنګه چې په Posix Threads کې)، نه په یو لاک کې. بیا یو څوک کولی شي دا د ټولو فیلسوفانو لپاره جوړ کړي. مګر حتی په دې فورمه کې، دا تاسو ته اجازه درکوي چې کوډ کم کړي.

ډیری فیلسوفان یا async / await

ښه، اوس موږ کولی شو په مؤثره توګه تارونه بند کړو. مګر که موږ ډیری فیلسوفان ولرو؟ ۱۰۰؟ 100؟ د مثال په توګه، موږ ویب سرور ته 10000 غوښتنې ترلاسه کړې. دا به په سر کې وي چې د هرې غوښتنې لپاره تار جوړ کړئ، ځکه دومره ډیری تارونه به موازي نه چلیږي. به یوازې دومره چلوي چې منطقي کورونه شتون ولري (زه 100000 لرم). او نور هرڅوک به یوازې سرچینې لرې کړي. د دې ستونزې یو حل د async / انتظار نمونه ده. د دې مفکوره دا ده چې فنکشن تار نه ساتي که اړتیا وي د دوام لپاره یو څه انتظار وکړي. او کله چې دا یو څه کوي، دا خپل اعدام بیا پیلوي (مګر اړینه نه ده چې په ورته تار کې!). زموږ په قضیه کې، موږ به د فورک لپاره انتظار وکړو.

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 تارونو څخه هر یو د 2ms لپاره بې کاره و. او د async / انتظار حل ټول 100 پرمخ وړل شوي، په اوسط ډول هر یو د 6.8 ثانیو انتظار سره. البته ، په ریښتیني سیسټمونو کې ، د 6 ثانیو لپاره بې کاره د منلو وړ ندي او دا غوره ده چې د دې په څیر ډیری غوښتنې پروسس نه کړئ. د مانیټر سره حل په هیڅ ډول د توزیع وړ نه و.

پایلې

لکه څنګه چې تاسو د دې کوچنیو مثالونو څخه لیدلی شئ، .NET د ډیری ترکیب جوړونې ملاتړ کوي. په هرصورت، دا تل روښانه نه ده چې څنګه یې کارول کیږي. زه امید لرم چې دا مقاله ګټوره وه. د اوس لپاره ، دا پای دی ، مګر لاهم ډیر په زړه پوري شیان پاتې دي ، د مثال په توګه ، د تار خوندي راټولول ، د TPL ډیټا فلو ، د عکس العمل برنامه کول ، د سافټویر لیږد ماډل ، او داسې نور.

سرچینې

سرچینه: www.habr.com

Add a comment