Falsafa masu wadataccen abinci ko shirye-shirye masu gasa a cikin NET

Falsafa masu wadataccen abinci ko shirye-shirye masu gasa a cikin NET

Bari mu kalli yadda shirye-shirye na lokaci ɗaya da na layi ɗaya ke aiki a cikin .Net, ta amfani da misalin matsalar masana falsafar cin abinci. Shirin shine kamar haka, daga zaren / aiki tare da tsari zuwa ƙirar mai wasan kwaikwayo (a cikin sassan masu zuwa). Labarin na iya zama da amfani ga sanin farko ko don sabunta ilimin ku.

Me yasa ma san yadda ake yin wannan? Transistor suna kaiwa mafi ƙarancin girmansu, dokar Moore tana bugun iyakar saurin haske, don haka ana ganin girma cikin lambobi; ana iya yin ƙarin transistor. A lokaci guda, adadin bayanai yana girma, kuma masu amfani suna tsammanin amsa nan da nan daga tsarin. A irin wannan yanayi, shirye-shiryen "al'ada", lokacin da muke da zaren aiwatarwa guda ɗaya, ba ya da tasiri. Muna buƙatar ko ta yaya mu magance matsalar kisa na lokaci ɗaya ko na lokaci ɗaya. Bugu da ƙari, wannan matsala ta wanzu a matakai daban-daban: a matakin zaren, a matakin tsari, a matakin inji akan hanyar sadarwa (tsarin rarraba). NET yana da inganci masu inganci, fasahar zamani da aka gwada don magance irin waɗannan matsalolin cikin sauri da inganci.

Manufar

Edsger Dijkstra ya tambayi wannan matsala ga dalibansa a baya a 1965. Tsarin da aka kafa shine kamar haka. Akwai takamaiman adadin (yawanci biyar) na masana falsafa da adadin cokula iri ɗaya. Zaune suke akan teburi, cokula masu yatsu a tsakanin su. Masu falsafa za su iya ci daga faranti na abinci marar iyaka, tunani ko jira. Don cin abinci, masanin falsafa yana buƙatar ɗaukar cokali biyu (na ƙarshe ya raba cokali mai yatsa tare da tsohon). Daukewa da ajiye cokali mai yatsa ayyuka ne daban-daban guda biyu. Duk masana falsafa sunyi shiru. Ayyukan shine don nemo irin wannan algorithm don su yi tunani kuma suna da wadataccen abinci ko da bayan shekaru 54.

Da farko, bari mu yi ƙoƙarin magance wannan matsala ta amfani da sararin samaniya. Cokali mai yatsu suna kwance akan teburin gama gari kuma masana falsafa kawai suna ɗaukar su lokacin da suke can su mayar da su. Wannan shine inda matsaloli tare da aiki tare suka taso, lokacin da daidai lokacin ɗaukar cokali mai yatsu? me za a yi idan babu toshe? da sauransu. Amma da farko, bari mu fara da masana falsafa.

Don fara zaren muna amfani da tafkin zaren ta Task.Run hanya:

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

An tsara tafkin zaren don inganta ƙirƙira da cire zaren. Wannan tafkin yana da jerin ayyuka kuma CLR yana ƙirƙira ko goge zaren ya danganta da adadin waɗannan ayyuka. Wahayi ɗaya don duk AppDomains. Wannan tafkin ya kamata a yi amfani da shi kusan ko da yaushe, saboda ... Babu buƙatar damuwa da ƙirƙira da share zaren, layin su, da sauransu. Kuna iya yin shi ba tare da tafkin ba, amma sai ku yi amfani da shi kai tsaye. Thread, Wannan yana da amfani ga lokuta lokacin da muke buƙatar canza fifikon zaren, lokacin da muke da dogon aiki, don zaren gaba, da dai sauransu.

Watau, System.Threading.Tasks.Task aji daya ne Thread, amma tare da kowane nau'i na dacewa: ikon ƙaddamar da aiki bayan toshe wasu ayyuka, mayar da su daga ayyuka, katse su cikin dacewa, da ƙari mai yawa. Da sauransu. Ana buƙatar su don tallafawa gine-gine async / jira (Tsarin Asynchronous na tushen Aiki, sugar syntactic don jiran ayyukan IO). Za mu yi magana game da wannan a gaba.

CancelationTokenSource a nan ya zama dole cewa zaren zai iya ƙare kansa a kan sigina daga zaren kira.

Abubuwan daidaitawa

An toshe masana falsafa

To, mun san yadda ake ƙirƙirar zaren, bari mu gwada abincin rana:

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

Anan za mu fara ƙoƙarin ɗaukar hagu sannan mu ɗauki cokali mai yatsa na dama, idan ya yi aiki, mu ci mu mayar da su. Ɗaukar cokali ɗaya shine atomic, watau. zaren biyu ba za su iya ɗaukar ɗaya a lokaci ɗaya ba (ba daidai ba: na farko ya karanta cewa cokali mai yatsa kyauta ne, na biyu kuma yana yin haka, na farko yana ɗauka, na biyu yana ɗauka). Domin wannan Interlocked.CompareExchange, wanda dole ne a aiwatar da shi ta amfani da umarnin sarrafawa (TSL, XCHG), wanda ke kulle wani yanki na ƙwaƙwalwar ajiya don karantawa da rubuce-rubucen jeri na atomic. Kuma SpinWait yayi daidai da ginin while(true) kawai tare da ɗan "sihiri" - zaren yana ɗaukar mai sarrafawa (Thread.SpinWait), amma wani lokacin yana wuce sarrafawa zuwa wani zaren (Thread.Yeild) ko kuma yayi barci (Thread.Sleep).

Amma wannan maganin ba ya aiki, saboda ... Zaren nan da nan (a cikin daƙiƙa guda a gare ni) an toshe su: duk masana falsafa sun ɗauki cokali mai yatsu na hagu, amma babu mai dama. Tsarin cokali mai yatsu sannan yana da ƙimar: 1 2 3 4 5.

Falsafa masu wadataccen abinci ko shirye-shirye masu gasa a cikin NET

A cikin hoton, toshe zaren (makulle). Green yana nuna kisa, ja yana nuna aiki tare, kuma launin toka yana nuna zaren yana barci. Lu'u-lu'u suna nuna lokacin ƙaddamar da Ayyuka.

Yunwar Falsafa

Ko da yake ba kwa buƙatar abinci mai yawa don tunani, yunwa na iya tilasta kowa ya bar falsafar. Mu yi kokari mu kwaikwayi halin da ake ciki na yunwar zare a cikin matsalarmu. Yunwa ita ce lokacin da zaren ya yi aiki, amma ba tare da wani aiki mai mahimmanci ba, a wasu kalmomi, yana da ma'auni guda ɗaya, kawai yanzu zaren ba ya barci, amma yana neman abin da za a ci, amma babu abinci. Don gujewa tarewa akai-akai, za mu mayar da cokali mai yatsa idan ba za mu iya ɗaukar wani ba.

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

Abin da ke da mahimmanci game da wannan lambar shine cewa biyu daga cikin masana falsafa huɗu sun manta da ajiye cokali mai yatsu na hagu. Kuma ya zama cewa sun fi cin abinci, wasu kuma sun fara jin yunwa, kodayake zaren suna da fifiko iri ɗaya. Anan ba su gama jin yunwa ba, domin... mugayen falsafa wani lokaci suna mayar da cokulansu. Sai ya zama cewa masu kirki suna cin kasa da na marasa kyau kamar sau 5. Don haka ƙaramin kuskure a cikin lambar yana haifar da faɗuwar aiki. A nan yana da kyau a lura cewa yanayi mai wuya yana yiwuwa lokacin da duk masana falsafa suka ɗauki cokali mai yatsu na hagu, babu dama, sun ajiye hagu, jira, sake ɗaukar hagu, da dai sauransu. Wannan yanayin kuma yunwa ce, kamar toshewar juna. Ba zan iya maimaita shi ba. Da ke ƙasa akwai hoto don yanayin da wasu mugayen falsafa guda biyu suka ɗauki cokali mai yatsu biyu, kuma biyu masu kyau suna fama da yunwa.

Falsafa masu wadataccen abinci ko shirye-shirye masu gasa a cikin NET

Anan zaka iya ganin cewa zaren wani lokaci yana farkawa kuma yayi ƙoƙarin samun albarkatu. Guda biyu na cores hudu ba su yin komai (Green Green sama).

Mutuwar Masanin Falsafa

To, wata matsala kuma da za ta iya katse liyafar cin abinci mai ɗaukaka na masana falsafa ita ce idan ɗaya daga cikinsu ya mutu kwatsam da cokali mai yatsu a hannunsa (kuma za a binne shi ta haka). Sa'an nan za a bar makwabta ba tare da abincin rana ba. Kuna iya fito da lambar misali don wannan harka da kanku, misali an jefar da shi NullReferenceException bayan malamin falsafa ya dauki cokali mai yatsu. Kuma, ta hanyar, banda ba za a kula da shi ba kuma lambar kiran ba za ta kama shi kawai (don wannan AppDomain.CurrentDomain.UnhandledException da sauransu). Don haka, ana buƙatar masu sarrafa kuskure a cikin zaren da kansu kuma tare da ƙarewa mai kyau.

Mai jira

To, ta yaya za mu magance wannan matsalar ta kulle-kulle, yunwa, da mace-mace? Za mu ƙyale masanin falsafa ɗaya kawai zuwa ga cokali mai yatsu, kuma za mu ƙara ware zaren zaren juna don wannan wuri. Yadda za a yi? A ce a kusa da masana falsafa akwai ma'aikaci wanda ya ba da izini ga wani masanin falsafa ya ɗauki cokali mai yatsa. Yadda za mu yi wannan ma'aikacin da kuma yadda masana falsafa za su yi masa tambayoyi masu ban sha'awa.

Hanya mafi sauƙi ita ce masana falsafa don kawai su tambayi ma'aikaci don samun damar shiga cokali mai yatsu. Wadancan. Yanzu masana falsafa ba za su jira cokali mai yatsa a kusa ba, amma jira ko tambayi ma'aikacin. Da farko muna amfani da sarari Mai amfani kawai don wannan; a ciki ba ma amfani da katsewa don kiran kowane hanya daga kernel (ƙari akan su a ƙasa).

Maganin sararin samaniya mai amfani

Anan za mu yi irin abin da muka yi a baya tare da cokali ɗaya da masana falsafa biyu, za mu juya cikin madauki mu jira. Amma yanzu zai zama duk masana falsafa kuma, kamar yadda yake, cokali ɗaya kawai, watau. za mu iya cewa kawai masanin falsafa wanda ya ɗauki wannan "cokali mai yatsa na zinariya" daga mai hidima zai ci. Don yin wannan, muna amfani da 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 wannan blocker ne, tare da, kusan magana, iri ɗaya while(true) { if (!lock) break; }, amma tare da ma fi "sihiri" fiye da a ciki SpinWait (wanda ake amfani da shi a can). Yanzu ya san yadda zai ƙidaya waɗanda suke jira, ya sa su barci kaɗan, da ƙari mai yawa. da dai sauransu Gaba ɗaya, yana yin duk abin da zai yiwu don ingantawa. Amma dole ne mu tuna cewa har yanzu wannan madauki mai aiki iri ɗaya ne wanda ke cinye albarkatun sarrafawa kuma yana riƙe da zare, wanda zai iya haifar da yunwa idan ɗaya daga cikin masana falsafa ya zama fifiko fiye da sauran, amma ba shi da cokali mai yatsa (Priority Inversion problem). ). Don haka, muna amfani da shi kawai don gajerun canje-canje a cikin ƙwaƙwalwar ajiyar da aka raba, ba tare da wani kira na ɓangare na uku ba, makullin gida, ko wasu abubuwan ban mamaki.

Falsafa masu wadataccen abinci ko shirye-shirye masu gasa a cikin NET

Zana don SpinLock. Rafukan suna "yaki" kullum don cokali mai yatsa na zinariya. Rashin gazawa yana faruwa - yankin da aka haskaka a cikin adadi. Ba a cika amfani da muryoyin ba: kusan 2/3 kawai ta waɗannan zaren guda huɗu.

Wani bayani anan zai kasance don amfani kawai Interlocked.CompareExchange tare da jira mai aiki iri ɗaya kamar yadda aka nuna a cikin lambar da ke sama (a cikin malaman falsafa masu fama da yunwa), amma wannan, kamar yadda aka riga aka faɗa, na iya haifar da toshewa.

a kan Interlocked yana da kyau a ce ba wai kawai ba CompareExchange, amma kuma sauran hanyoyin karatun atomic DA rubutu. Kuma ta hanyar maimaita canjin, idan wani zaren ya sami damar yin canje-canjensa (karanta 1, karanta 2, rubuta 2, rubuta 1 mara kyau), ana iya amfani da shi don rikitattun canje-canje zuwa ƙima ɗaya (Interlocked Anything pattern).

Yanayin Kernel mafita

Don guje wa ɓarna albarkatu a madauki, bari mu kalli yadda ake toshe zaren. Wato, ci gaba da misalinmu, bari mu ga yadda ma’aikacin ya sa malamin falsafa ya kwana kuma ya tashe shi kawai idan ya cancanta. Da farko, bari mu kalli yadda ake yin hakan ta hanyar yanayin kernel na tsarin aiki. Duk tsarin da ke wurin galibi yana ƙarewa suna yin hankali fiye da waɗanda ke cikin sararin mai amfani. Sannu a hankali sau da yawa, misali AutoResetEvent watakila sau 53 a hankali SpinLock [Richter]. Amma tare da taimakonsu, zaku iya aiki tare da tafiyar matakai a cikin dukkan tsarin, sarrafawa ko a'a.

Tsarin asali anan shine semaphore, wanda Dijkstra ya gabatar fiye da rabin karni da suka gabata. Semaphore shine, a sauƙaƙe, ingantaccen lamba wanda tsarin ke sarrafawa, da kuma ayyuka guda biyu akansa - karuwa da raguwa. Idan ba zai yiwu a rage sifili ba, to an toshe zaren kira. Lokacin da lambar ta ƙara da wasu zaren / tsari mai aiki, to za a wuce zaren kuma an sake rage semaphore ta lambar da aka wuce. Kuna iya tunanin jiragen kasa a cikin kwalabe tare da semaphore. NET yana ba da gine-gine da yawa tare da ayyuka iri ɗaya: AutoResetEvent, ManualResetEvent, Mutex da kaina Semaphore. Za mu yi amfani AutoResetEvent, wannan shine mafi sauƙi daga cikin waɗannan gine-gine: kawai dabi'u biyu 0 da 1 (ƙarya, gaskiya). Hanyarta WaitOne() yana toshe zaren kira idan ƙimar ta kasance 0, idan kuma 1, to rage shi zuwa 0 kuma ya tsallake shi. Hanya Set() yana ƙaruwa zuwa 1 kuma yana barin mutum ɗaya ta hanyar, wanda ya sake raguwa zuwa 0. Yana aiki kamar juyi a cikin jirgin karkashin kasa.

Bari mu rikitar da mafita kuma mu yi amfani da toshewa ga kowane masanin falsafa, kuma ba gaba ɗaya ba. Wadancan. Yanzu masana falsafa da yawa za su iya ci gaba ɗaya, kuma ba ɗaya ba. Amma mun sake toshe hanyar shiga teburin domin mu ɗauki cokali mai yatsu daidai, muna guje wa yanayin tsere.

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

Don fahimtar abin da ke faruwa a nan, la'akari da lamarin lokacin da masanin falsafa ya kasa ɗaukar cokali mai yatsu, to ayyukansa zasu kasance kamar haka. Yana jiran samun damar zuwa teburin. Bayan ya karbe shi, sai ya yi kokarin daukar cokali mai yatsu. Bai yi aiki ba. Yana ba da damar shiga tebur (keɓance juna). Kuma ya wuce “juyawa” (AutoResetEvent) (da farko suna budewa). Ya sake fadawa cikin sake zagayowar, saboda ba shi da cokali mai yatsu. Yana ƙoƙari ya ɗauke su ya tsaya a "juyawa". Wasu maƙwabcin da suka fi sa'a zuwa dama ko hagu, bayan sun gama cin abinci, za su buɗe wa masanin falsafar mu ta “buɗe juyowarsa.” Masanin iliminmu ya bi ta (kuma yana rufe bayansa) a karo na biyu. Ya yi ƙoƙari a karo na uku don ɗaukar cokali mai yatsu. Nasara Kuma ya bi ta juyowa don cin abincin rana.

Lokacin da aka sami kurakurai bazuwar a cikin irin wannan lambar (suna wanzuwa koyaushe), misali, za a ƙayyade maƙwabci ba daidai ba ko kuma a ƙirƙiri abu ɗaya. AutoResetEvent ga kowa (Enumerable.Repeat), to, masana falsafa za su jira masu haɓakawa, saboda Nemo kurakurai a cikin irin wannan lambar aiki ne mai wahala. Wata matsala tare da wannan maganin ita ce ba ta da tabbacin cewa wasu masana falsafa ba za su ji yunwa ba.

Matakan mafita

Mun kalli hanyoyi guda biyu don daidaitawa, lokacin da muka tsaya cikin yanayin mai amfani kuma muka juya cikin madauki da kuma lokacin da muka toshe zaren ta kernel. Hanyar farko tana da kyau ga gajerun tubalan, na biyu don dogon lokaci. Sau da yawa kuna buƙatar fara jira a taƙaice don canji ya canza a cikin madauki, sannan ku toshe zaren lokacin jira ya daɗe. Ana aiwatar da wannan hanyar a cikin abin da ake kira. matasan kayayyaki. Yana da gine-gine iri ɗaya kamar yanayin kernel, amma yanzu tare da madauki-yanayin mai amfani: SemaphorSlim, ManualResetEventSlim da dai sauransu Mafi mashahuri zane a nan shi ne Monitor, saboda a cikin C # akwai sananne lock syntax. Monitor wannan semaphore iri ɗaya ne tare da matsakaicin ƙimar 1 (mutex), amma tare da tallafi don jira a cikin madauki, recursion, yanayin Canjin yanayin (ƙari akan wannan ƙasa), da dai sauransu. Bari mu kalli mafita tare da shi.

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

Anan mun sake toshe teburin gaba ɗaya daga shiga cokula masu yatsu, amma yanzu muna buɗe duk zaren lokaci ɗaya, maimakon maƙwabta lokacin da wani ya gama cin abinci. Wadancan. da farko wani ya ci ya toshe makwabcinsa, da wannan ya gama, amma yana so ya sake cin abinci nan da nan, sai ya shiga shingen ya tadda maƙwabcinsa, domin. lokacin jiransa ya ragu.

Ta haka ne muke guje wa ƙulle-ƙulle da yunwar wasu masana falsafa. Muna amfani da madauki don jira na ɗan gajeren lokaci kuma mu toshe zaren na dogon lokaci. Cire katanga kowa a lokaci guda yana da hankali fiye da idan an buɗe maƙwabcin, kamar yadda yake cikin bayani tare da AutoResetEvent, amma bambancin bai kamata ya zama babba ba, saboda zaren dole su kasance cikin yanayin mai amfani tukuna.

У lock syntax yana da wasu abubuwan ban mamaki marasa daɗi. An ba da shawarar yin amfani da shi Monitor kai tsaye [Richter] [Eric Lippert]. Daya daga cikinsu shi ne lock kullum yana fitowa Monitor, ko da akwai wani togiya, sa'an nan kuma wani zare iya canza yanayin da shared memory. A irin waɗannan lokuta, sau da yawa yana da kyau a shiga cikin matsi ko ta yaya a dakatar da shirin. Wani abin mamaki shi ne cewa Monitor yana amfani da agogon agogo (SyncBlock), waɗanda suke a cikin dukkan abubuwa. Don haka, idan an zaɓi abin da bai dace ba, zaku iya samun maƙulli cikin sauƙi (misali, idan kun kulle igiyar da aka haɗa). Kullum muna amfani da wani abu mai ɓoye don wannan.

Tsarin Canjin Yanayin Yana ba ku damar aiwatar da tsammanin wasu yanayi mai rikitarwa a takaice. A cikin NET bai cika ba, a ganina, saboda ... A ka'idar, ya kamata a sami jerin layi da yawa akan masu canji da yawa (kamar yadda yake cikin Posix Threads), kuma ba akan kulle ɗaya ba. Sa'an nan kuma zai yiwu a sanya su ga dukan masana falsafa. Amma ko da a cikin wannan tsari yana ba ku damar rage lambar.

Yawancin masana falsafa ko async / await

To, yanzu za mu iya toshe zaren yadda ya kamata. Amma idan muna da masana falsafa da yawa fa? 100? 10000? Misali, mun sami buƙatun 100000 zuwa sabar gidan yanar gizo. Ƙirƙirar zaren kowane buƙata zai yi tsada, saboda da yawa zaren ba za a kashe a layi daya. Kamar yadda yawancin ma'auni na ma'ana za a kashe (Ina da 4). Kuma kowa zai kwashe albarkatun kawai. Magani ɗaya ga wannan matsalar shine tsarin async / jira. Tunaninsa shine cewa aiki baya riƙe zaren idan yana buƙatar jira wani abu ya ci gaba. Kuma idan wani abu ya faru, sai ya koma aiwatar da shi (amma ba lallai ba ne a cikin zare ɗaya!). A cikin yanayinmu, za mu jira cokali mai yatsa.

SemaphoreSlim yana da wannan WaitAsync() hanya. Anan akwai aiwatarwa ta amfani da wannan tsari.

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

Hanyar tare da async / await an fassara shi zuwa injin ma'auni mai iyaka, wanda nan da nan ya dawo da ciki Task. Ta hanyar shi, zaku iya jira hanyar don kammalawa, soke ta, da duk abin da zaku iya yi tare da Task. A cikin hanyar, injin jiha yana sarrafa kisa. Maganar ƙasa ita ce, idan ba a jinkirta ba, to aiwatarwa yana aiki tare, idan kuma akwai, sai a saki zaren. Don ƙarin fahimtar wannan, yana da kyau a kalli wannan na'ura na jihar. Kuna iya ƙirƙirar sarƙoƙi daga waɗannan async / await hanyoyin.

Mu gwada shi. Aikin masana falsafa 100 akan na'ura mai ma'ana 4, 8 seconds. Maganin da ya gabata tare da Monitor kawai ya aiwatar da zaren 4 na farko kuma bai aiwatar da sauran ba kwata-kwata. Kowanne daga cikin waɗannan zaren guda 4 ya yi aiki kusan 2ms. Kuma maganin async / jira ya yi duka 100, tare da matsakaicin 6.8 seconds kowane jira. Tabbas, a cikin ainihin tsarin, yin aiki na tsawon daƙiƙa 6 ba abin yarda bane kuma yana da kyau kada a aiwatar da buƙatun da yawa ta wannan hanyar. Maganin tare da Monitor ya juya ya zama ba mai iya daidaitawa kwata-kwata.

ƙarshe

Kamar yadda kuke gani daga waɗannan ƙananan misalan, .NET yana goyan bayan ginin aiki tare da yawa. Duk da haka, ba koyaushe ake bayyana yadda ake amfani da su ba. Ina fata wannan labarin ya taimaka. Muna tattara wannan don yanzu, amma har yanzu akwai sauran abubuwa masu ban sha'awa da yawa da suka rage, misali, tarin amintaccen zaren, TPL Dataflow, Shirye-shiryen Reactive, Samfurin Kasuwancin Software, da sauransu.

Sources

source: www.habr.com

Add a comment