Filsuf well-Fed atanapi kalapa .NET Programming

Filsuf well-Fed atanapi kalapa .NET Programming

Hayu urang tingali kumaha program serentak sareng paralel tiasa dianggo dina .Net, nganggo Masalah Filsuf Dining sabagé conto. Rencanana nyaéta ieu, tina sinkronisasi benang / prosés, dugi ka modél aktor (dina bagian-bagian di handap ieu). Tulisan éta tiasa mangpaat pikeun kenalan anu munggaran atanapi pikeun nyegerkeun pangaweruh anjeun.

Naha ngalakukeun eta pisan? Transistor ngahontal ukuran minimum maranéhanana, hukum Moore rests on watesan laju cahaya sahingga ngaronjatna dititénan dina jumlah, leuwih transistor bisa dijieun. Dina waktos anu sami, jumlah data naék, sareng pangguna ngarepkeun réspon langsung tina sistem. Dina kaayaan kitu, "normal" programming, lamun urang boga hiji executing thread, geus euweuh éféktif. Anjeun kedah kumaha bae ngajawab masalah palaksanaan simultaneous atawa sakaligus. Leuwih ti éta, masalah ieu aya dina tingkat béda: dina tingkat threads, dina tingkat prosés, dina tingkat mesin dina jaringan (sistem disebarkeun). .NET gaduh kualitas luhur, téknologi anu diuji waktos pikeun gancang sareng éfisién ngarengsekeun masalah sapertos kitu.

tugas

Edsger Dijkstra ngajukeun masalah ieu ka murid-muridna ti taun 1965. Rumusan anu ditetepkeun nyaéta kieu. Aya sababaraha (biasana lima) jumlah filsuf sareng jumlah garpu anu sami. Aranjeunna diuk dina méja buleud, garpu antara aranjeunna. Filsuf bisa dahar tina piring maranéhanana dahareun sajajalan, pikir atawa antosan. Pikeun tuang filsuf, anjeun kedah nyandak dua garpu (anu terakhir ngabagi garpu sareng anu munggaran). Nyokot sareng nempatkeun garpu mangrupikeun dua tindakan anu misah. Kabéh filsuf jempé. Tugasna nyaéta milarian algoritma sapertos kitu anu sadayana bakal pikir sareng pinuh sanajan saatos 54 taun.

Kahiji, hayu urang cobaan pikeun ngajawab masalah ieu ngaliwatan pamakéan spasi dibagikeun. Garpu ngagolér dina méja umum sareng para filsuf ngan saukur nyandak aranjeunna nalika aranjeunna sareng nempatkeun deui. Di dieu aya masalah sareng sinkronisasi, iraha persisna nyandak surebets? kumaha upami teu aya garpu? jsb Tapi ke heula, hayu urang mimitian filosof.

Pikeun ngamimitian threads, urang ngagunakeun thread pool ngaliwatan Task.Run métode:

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

Kolam benang dirancang pikeun ngaoptimalkeun nyiptakeun sareng ngahapus benang. Kolam renang ieu ngagaduhan antrian sareng tugas sareng CLR nyiptakeun atanapi ngahapus benang gumantung kana jumlah tugas ieu. Hiji kolam renang pikeun sakabéh AppDomains. kolam renang ieu kudu dipaké ampir sok, sabab. teu perlu repot jeung nyieun, mupus threads, antrian maranéhanana, jsb Ieu mungkin tanpa kolam renang, tapi mangka anjeun kudu make eta langsung Thread, Ieu mangpaat pikeun kasus nalika anjeun kudu ngarobah prioritas thread a, nalika urang boga operasi panjang, pikeun thread Foreground, jsb.

Istilah sanésna, System.Threading.Tasks.Task kelas téh sarua Thread, Tapi kalayan sagala sorts conveniences: kamampuhan pikeun ngajalankeun tugas sanggeus blok tugas sejen, balik aranjeunna tina fungsi, merenah ngaganggu aranjeunna, sareng nu sanesna. jsb Éta diperlukeun pikeun ngarojong async / await constructions (Pola Asynchronous dumasar-Tugas, gula sintaksis keur ngantosan operasi IO). Urang bakal ngobrol ngeunaan ieu engké.

CancelationTokenSource Ieu diperlukeun ku kituna thread bisa ngeureunkeun sorangan dina sinyal thread nelepon.

Masalah singkronisasi

Filsuf diblokir

Oké, urang nyaho kumaha carana nyieun threads, hayu urang coba dahar beurang:

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

Di dieu urang cobaan heula nyandak garpu kénca, teras garpu katuhu, sareng upami éta hasil, teras tuang teras nahan deui. Nyandak hiji garpu nyaéta atom, i.e. dua threads teu bisa nyokot hiji sakaligus (salah: kahiji berbunyi yén garpu bébas, kadua - teuing, kahiji nyokot, kadua nyokot). Kanggo ieu Interlocked.CompareExchange, nu kudu dilaksanakeun kalawan instruksi processor (TSL, XCHG), anu ngonci sapotong mémori pikeun maca sareng nyerat urutan atom. Sareng SpinWait sami sareng ngawangun while(true) ngan kalawan saeutik "magic" - thread nyokot processor (Thread.SpinWait), tapi kadang mindahkeun kontrol ka thread sejen (Thread.Yeild) atawa saré (Thread.Sleep).

Tapi solusi ieu teu jalan, sabab ngalir pas (pikeun kuring dina sadetik) diblokir: kabéh filsuf nyokot garpu kénca maranéhanana, tapi teu katuhu. Array forks teras gaduh nilai: 1 2 3 4 5.

Filsuf well-Fed atanapi kalapa .NET Programming

Dina gambar, blocking threads (deadlock). Héjo - palaksanaan, beureum - sinkronisasi, kulawu - benang keur saré. The rhombuses nunjukkeun waktu mimiti Tugas.

Lapar para Filsuf

Sanajan teu perlu mikir utamana loba dahareun, tapi lapar ngajadikeun saha nyerah filsafat. Hayu urang coba mun simulate kaayaan kalaparan threads dina masalah urang. Kalaparan nyaéta nalika benang dijalankeun, tapi tanpa padamelan anu penting, dina basa sanés, ieu mangrupikeun jalan buntu anu sami, ngan ayeuna benang henteu bobo, tapi aktip milarian tuangeun, tapi teu aya tuangeun. Pikeun ngahindarkeun sering ngahalangan, urang bakal nempatkeun garpu deui upami urang henteu tiasa nyandak anu sanés.

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

Hal penting ngeunaan kode ieu nyaéta yén dua ti opat filsuf poho nempatkeun handap garpu kénca maranéhanana. Sareng tétéla yén aranjeunna tuang langkung seueur tuangeun, sedengkeun anu sanésna mimiti kalaparan, sanaos benang ngagaduhan prioritas anu sami. Di dieu maranéhna teu sagemblengna kalaparan, sabab. filsuf goréng nempatkeun forks maranéhna deui kadang. Tétéla jalma alus dahar ngeunaan 5 kali kirang ti goréng. Jadi kasalahan leutik dina kode ngabalukarkeun turunna kinerja. Éta ogé sia ​​noting dieu yén kaayaan langka mungkin lamun sakabeh filsuf nyokot garpu kénca, euweuh hiji katuhu, aranjeunna nempatkeun kénca, antosan, nyandak kénca deui, jsb. Kaayaan ieu ogé kalaparan, langkung sapertos jalan buntu. Kuring gagal malikan deui. Di handap ieu gambar pikeun kaayaan dimana dua filsuf goréng geus nyokot duanana garpu jeung dua alus keur kalaparan.

Filsuf well-Fed atanapi kalapa .NET Programming

Di dieu anjeun tiasa ningali yén benang kadang hudang sareng nyobian kéngingkeun sumberna. Dua tina opat inti teu ngalakukeun nanaon (grafik héjo di luhur).

Pupusna Filsuf

Nya, masalah sanés anu tiasa ngaganggu tuangeun tuangeun para filsuf anu mulya nyaéta upami salah sahijina ujug-ujug maot kalayan garpu dina pananganna (sareng aranjeunna bakal ngubur anjeunna sapertos kitu). Lajeng tatanggana bakal ditinggalkeun tanpa dinner. Anjeun tiasa datang nepi ka hiji conto kode pikeun hal ieu sorangan, contona, eta dialungkeun kaluar NullReferenceException sanggeus filsuf nyokot garpu. Sareng, ku jalan kitu, pangecualian moal diurus sareng kodeu nélépon henteu ngan ukur nyekel éta (pikeun ieu AppDomain.CurrentDomain.UnhandledException jeung sajabana). Ku alatan éta, panangan kasalahan anu diperlukeun dina threads sorangan sarta kalawan terminasi anggun.

Palayan

Oké, kumaha urang ngajawab deadlock ieu, kalaparan, jeung masalah maot? Urang bakal ngidinan ngan hiji filsuf pikeun ngahontal garpu, nambahkeun silih pangaluaran threads pikeun tempat ieu. Kumaha cara ngalakukeunana? Anggap palayan nangtung gigireun filsuf, anu mere idin ka salah sahiji filsuf nyandak garpu. Kumaha urang ngadamel palayan ieu sareng kumaha filsuf bakal naroskeun anjeunna, patarosan anu pikaresepeun.

Cara pangbasajanna nyaéta nalika para filsuf ngan saukur bakal naroskeun ka palayan pikeun aksés kana garpu. Jelema. ayeuna filsuf moal ngadagoan garpu caket dieu, tapi antosan atanapi nanya ka palayan. Mimitina, kami ngan ukur nganggo Spasi Pamaké pikeun ieu, di dinya kami henteu nganggo interrupts pikeun nelepon prosedur naon waé tina kernel (ngeunaan aranjeunna di handap).

Solusi dina rohangan pamaké

Di dieu urang bakal lakonan hal nu sarua salaku urang biasa ngalakukeun kalawan hiji garpu jeung dua filosof, urang bakal spin dina siklus sarta antosan. Tapi ayeuna eta bakal kabeh filsuf jeung, sakumaha éta, ngan hiji garpu, i.e. Ieu bisa disebutkeun yen ngan filsuf anu nyandak ieu "garpu emas" ti palayan bakal dahar. Pikeun ieu kami nganggo 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 ieu blocker a, kalawan, kasarna diomongkeun, sami while(true) { if (!lock) break; }, Tapi kalawan malah leuwih "magic" ti di SpinWait (anu dipaké aya). Ayeuna anjeunna terang kumaha ngitung jalma anu ngantosan, nempatkeun aranjeunna bobo sakedik, sareng seueur deui. jsb Sacara umum, teu sagalana mungkin ngaoptimalkeun. Tapi urang kudu inget yen ieu téh masih siklus aktif sarua nu eats up sumberdaya processor na ngajaga aliran, nu bisa ngakibatkeun kalaparan lamun salah sahiji filsuf jadi prioritas leuwih ti batur, tapi teu boga garpu emas (prioritas Inversion masalah). . Ku alatan éta, kami nganggo éta ngan ukur pikeun parobahan anu pondok pisan dina mémori anu dibagi, tanpa aya telepon pihak katilu, konci bersarang, sareng kejutan sanés.

Filsuf well-Fed atanapi kalapa .NET Programming

Ngagambar pikeun SpinLock. Aliran-aliran terus "perang" pikeun garpu emas. Aya gagal - dina gambar, wewengkon dipilih. Inti henteu dianggo sapinuhna: ngan ukur 2/3 ku opat utas ieu.

Solusi anu sanés di dieu nyaéta ngan ukur nganggo Interlocked.CompareExchange jeung antosan aktif sarua ditémbongkeun saperti dina kode di luhur (dina filsuf kalaparan), tapi ieu, sakumaha geus ngomong, téoritis bisa ngakibatkeun blocking.

dina Interlocked Ieu kudu dicatet yén aya teu ngan CompareExchange, tapi ogé métode séjén pikeun atom maca AND nulis. Jeung ngaliwatan pengulangan parobahan, bisi thread sejen boga waktu pikeun nyieun parobahanana (baca 1, baca 2, nulis 2, nulis 1 goréng), bisa dipaké pikeun parobahan kompléks kana nilai tunggal (Pola Interlocked Naon bae). .

Solusi Kernel Mode

Pikeun ngahindarkeun sumber daya dina loop, hayu urang tingali kumaha urang tiasa meungpeuk benang. Dina basa sejen, neraskeun conto urang, hayu urang tingali kumaha palayan nu nempatkeun filsuf saré sarta wakes anjeunna nepi ngan lamun perlu. Mimiti, hayu urang tingali kumaha ngalakukeun ieu ngaliwatan mode kernel sistem operasi. Sadaya struktur di dinya sering langkung laun tibatan anu aya dina rohangan pangguna. Sababaraha kali laun, contona AutoResetEvent meureun 53 kali leuwih laun SpinLock [Richter]. Tapi kalayan bantosanana, anjeun tiasa nyingkronkeun prosés sapanjang sistem, diurus atanapi henteu.

Konstruksi dasar di dieu nyaéta semafor anu diusulkeun ku Dijkstra langkung ti satengah abad ka tukang. A semafor, kantun nempatkeun, integer positif dikelola ku sistem, sarta dua operasi dina eta, increment jeung decrement. Upami gagal ngirangan, nol, teras benang telepon diblokir. Lamun jumlah ieu incremented ku sababaraha thread aktip séjén / prosés, lajeng threads anu skipped na semafor deui decremented ku jumlah kaliwat. Hiji tiasa ngabayangkeun karéta dina bottleneck kalawan semafor a. .NET nawarkeun sababaraha constructs kalawan pungsi nu sarupa: AutoResetEvent, ManualResetEvent, Mutex sareng kuring sorangan Semaphore. Urang bakal ngagunakeun AutoResetEvent, ieu pangbasajanna tina konstruksi ieu: ngan dua nilai 0 jeung 1 (palsu, leres). Métode nya WaitOne() meungpeuk thread nélépon lamun nilai éta 0, sarta lamun 1, lowers ka 0 na skips eta. Hiji métode Set() raises ka 1 sarta ngidinan hiji palayan ngaliwatan, anu deui lowers ka 0. Tindakan kawas turnstile subway.

Hayu urang ngahesekeun solusi sareng nganggo konci pikeun tiap filsuf, sareng henteu sakaligus. Jelema. ayeuna tiasa aya sababaraha filsuf sakaligus, sareng henteu hiji. Tapi urang deui meungpeuk aksés ka méja guna neuleu, Ngahindarkeun ras (kaayaan lomba), nyandak 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();
}

Ngartos naon anu lumangsung di dieu, mertimbangkeun kasus nalika filsuf gagal nyandak garpu, lajeng lampah na bakal kieu. Anjeunna ngantosan aksés ka méja. Saatos nampi éta, anjeunna nyobian nyandak garpu. Teu hasil. Méré aksés ka tabél (silih pangaluaran). Sareng ngalangkungan "turnstile" na (AutoResetEvent) (aranjeunna mimitina dibuka). Éta asup kana siklus deui, sabab anjeunna teu gaduh garpu. Anjeunna nyobian nyandak aranjeunna sareng eureun di "turnstile" na. Sababaraha tatangga leuwih untung di katuhu atawa kénca, sanggeus réngsé dahar, muka konci filsuf urang, "muka turnstile-Na." Filsuf urang ngaliwat éta (sareng nutup di tukangeunana) pikeun kadua kalina. Anjeunna nyobian pikeun katilu kalina nyandak garpu. Sing salamet. Sarta anjeunna ngaliwatan turnstile na dine.

Nalika aya kasalahan acak dina kode sapertos kitu (aranjeunna sok aya), contona, tatanggana henteu leres-leres dieusian atanapi obyék anu sami diciptakeun. AutoResetEvent pikeun sadayana (Enumerable.Repeat), teras para filosof bakal ngantosan pamekar, sabab Milarian kasalahan dina kode sapertos kitu mangrupikeun tugas anu sesah. Masalah sanésna sareng solusi ieu nyaéta yén éta henteu ngajamin yén sababaraha filsuf moal kalaparan.

Solusi Hibrid

Kami parantos ningali dua pendekatan kana waktosna, nalika urang tetep dina modeu pangguna sareng loop, sareng nalika urang meungpeuk benang ngaliwatan kernel. Metodeu kahiji hadé pikeun konci pondok, anu kadua pikeun anu panjang. Ieu sering perlu sakeudeung ngadagoan hiji variabel robah dina loop a, lajeng meungpeuk thread lamun antosan panjang. pendekatan ieu dilaksanakeun dina disebut. struktur hibrid. Ieu mangrupikeun konstruksi anu sami sareng mode kernel, tapi ayeuna nganggo loop modeu pangguna: SemaphorSlim, ManualResetEventSlim jsb Desain pang populerna di dieu nyaeta Monitor, sabab dina C # aya hiji well-dipikawanoh lock sintaksis. Monitor ieu teh semafor sarua jeung nilai maksimum 1 (mutex), tapi kalayan rojongan pikeun antosan di loop a, recursion, pola Kaayaan Variabel (nu langkung lengkep ihwal nu handap), jsb Hayu urang nempo solusi kalawan eta.

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

Di dieu urang deui meungpeuk sakabéh méja pikeun aksés ka garpu, tapi ayeuna urang muka blokiran sakabeh threads sakaligus, teu tatanggana lamun batur rengse dahar. Jelema. kahiji, batur eats jeung blok tatanggana, jeung lamun batur ieu rengse, tapi hayang dahar deui katuhu jauh, manéhna mana kana blocking jeung wakes nepi tatanggana, sabab. waktos ngantosan na kirang.

Ieu kumaha urang nyingkahan deadlocks jeung kalaparan sababaraha filsuf. Urang ngagunakeun loop pikeun nungguan pondok tur meungpeuk thread pikeun hiji panjang. Ngabuka blokir sadayana sakaligus langkung laun tibatan upami tatanggana diblokir, sapertos dina solusi AutoResetEvent, Tapi bédana teu kudu badag, sabab threads kudu tetep dina modeu pamaké munggaran.

У lock sintaksis gaduh kejutan jahat. Nyarankeun ngagunakeun Monitor langsung [Richter] [Eric Lippert]. Salah sahijina nyaéta éta lock salawasna kaluar tina Monitor, sanajan aya pengecualian, bisi nu thread sejen bisa ngarobah kaayaan memori dibagikeun. Dina kasus sapertos kitu, éta sering langkung saé pikeun nuju jalan buntu atanapi kumaha waé anu aman ngeureunkeun program. Kaheranan sanésna nyaéta Monitor nganggo blok sinkronisasi (SyncBlock), nu aya dina sakabéh objék. Ku alatan éta, lamun hiji obyék pantes dipilih, anjeun bisa kalayan gampang meunang deadlock a (contona, lamun konci dina string interned). Kami nganggo objék anu sok disumputkeun pikeun ieu.

Pola Variabel Kondisi ngamungkinkeun anjeun langkung ringkes ngalaksanakeun ekspektasi sababaraha kaayaan anu rumit. Dina .NET, éta teu lengkep, dina pamadegan mah, sabab dina tiori, kudu aya sababaraha antrian dina sababaraha variabel (sakumaha dina Posix Threads), teu on hiji lok. Lajeng hiji bisa nyieun eta pikeun sakabéh filsuf. Tapi sanajan dina formulir ieu, eta ngidinan Anjeun pikeun ngurangan kode.

loba filsuf atawa async / await

Oké, ayeuna urang bisa éféktif meungpeuk threads. Tapi kumaha lamun urang boga loba filsuf? 100? 10000? Contona, urang narima 100000 requests ka web server. Ieu bakal overhead nyieun thread pikeun tiap pamundut, sabab jadi loba threads moal ngajalankeun paralel. Ngan bakal ngajalankeun saloba aya cores logis (kuring boga 4). Jeung dulur sejenna ngan bakal nyokot jauh sumberdaya. Salah sahiji solusi pikeun masalah ieu nyaéta pola async / await. Gagasanna nyaéta yén fungsina henteu nahan benang upami éta kedah ngantosan anu diteruskeun. Sarta lamun ngalakukeun hal, eta neruskeun palaksanaan na (tapi teu merta dina thread sarua!). Dina hal urang, urang bakal ngantosan garpu.

SemaphoreSlim boga keur ieu WaitAsync() métode. Ieu mangrupikeun palaksanaan nganggo pola ieu.

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

Métode kalawan async / await ditarjamahkeun kana mesin kaayaan tricky nu langsung balik internal na Task. Ngaliwatan éta, anjeun tiasa ngantosan parantosan metodeu, ngabatalkeunana, sareng sadayana anu anjeun tiasa laksanakeun sareng Tugas. Di jero metodeu, mesin kaayaan ngatur palaksanaan. Garis handap nyaéta yén lamun teu aya reureuh, teras palaksanaan sinkron, sarta lamun aya, lajeng thread dileupaskeun. Pikeun pamahaman hadé ngeunaan ieu, éta hadé pikeun nempo mesin kaayaan ieu. Anjeun tiasa nyiptakeun ranté tina ieu async / await métode.

Hayu urang nguji. Karya 100 filsuf dina mesin kalawan 4 cores logis, 8 detik. Solusi saméméhna sareng Monitor ngan ukur ngajalankeun 4 utas anu munggaran sareng sésana henteu jalan pisan. Masing-masing tina 4 utas ieu dianggurkeun sakitar 2ms. Sareng solusi async / await ngajalankeun sadayana 100, kalayan rata-rata antosan masing-masing 6.8 detik. Tangtosna, dina sistem nyata, dianggurkeun pikeun 6 detik henteu katampi sareng langkung saé henteu ngolah seueur paménta sapertos kieu. Solusi sareng Monitor tétéla henteu tiasa diskalakeun pisan.

kacindekan

Sakumaha anjeun tiasa tingali tina conto leutik ieu, .NET ngarojong loba constructs sinkronisasi. Sanajan kitu, teu salawasna atra kumaha ngagunakeun aranjeunna. Kuring miharep artikel ieu mantuan. Pikeun ayeuna, ieu tungtungna, tapi masih aya loba hal metot ditinggalkeun, contona, kumpulan thread-aman, TPL Dataflow, programming réaktif, modél Transaksi Software, jsb.

sumber

sumber: www.habr.com

Tambahkeun komentar