Conditievariabelen, semaforen en deadlocks

Vergrendelingen voorkomen dat threads elkaar in de weg zitten, maar threads moeten ook op elkaar kunnen wachten. Dit materiaal laat zien hoe conditievariabelen en semaforen een thread laten slapen tot gegevens gereed zijn, een plaats vrij is of een andere thread klaar is. Je gebruikt ze om het producent-consumentprobleem en het lezers-schrijversprobleem op te lossen. Daarna komen fouten in echte concurrente code aan bod: schendingen van atomiciteit, volgordeschendingen en deadlocks, met manieren om deadlocks te voorkomen, te vermijden en te detecteren. Tot slot volgt gebeurtenisgestuurde gelijktijdigheid, waarbij één thread een gebeurtenislus uitvoert in plaats van veel threads te gebruiken. De voorbeelden gebruiken C met POSIX-threads en worden stap voor stap gevolgd.

GemiddeldLes 6 van 9 in Cursus Besturingssystemen: processen, geheugen, threads en bestandssystemen

Wat je leert

  1. Laat een thread slapen tot een conditie op gedeelde gegevens waar wordt, met een conditievariabele samen met een vergrendeling en een toestandsvariabele. Gebruik dit patroon om een correcte begrensde buffer te bouwen.
    • Wacht op een conditie in plaats van actief te wachten
      Leg uit waarom een vergrendeling alleen een thread niet op een gebeurtenis kan laten wachten en gebruik wachten en signaleren met een vergrendeling en een toestandsvariabele, zodat geen weksignaal verloren gaat.
    • Controleer condities opnieuw en kies of je één of alle wachtende threads wekt
      Pas Mesa-semantiek toe door de conditie opnieuw te controleren in een while-lus, kies of je één of alle wachtende threads wekt en herken dezelfde regels in monitors en Java.
    • Los het producent-consumentprobleem op met conditievariabelen
      Bouw een begrensde buffer die producent- en consumentthreads delen, volg waarom naïeve versies falen en herstel ze met while-lussen en twee conditievariabelen.
  2. Gebruik de semafoor, één enkele primitieve bouwsteen, als vergrendeling, als signaal tussen threads en als teller voor bronnen. Pas die toe op de begrensde buffer en op een lezers-schrijversvergrendeling.
    • Definieer een semafoor en gebruik die als vergrendeling
      Beschrijf hoe de waarde van een semafoor verandert door wacht- en vrijgavebewerkingen, kies de beginwaarde en gebruik een binaire semafoor voor wederzijdse uitsluiting.
    • Bepaal de volgorde van threads en begrens gelijktijdige uitvoering met semaforen
      Gebruik een semafoor die bij nul begint om één thread op een andere te laten wachten, en een telsemafoor om te begrenzen hoeveel threads tegelijk een bron gebruiken.
    • Bouw een begrensde buffer met semaforen
      Los het producent-consumentprobleem op met twee telsemaforen en een mutex, en leg uit waarom de volgorde waarin ze worden verworven bepaalt of de buffer werkt of een deadlock ontstaat.
    • Deel gegevens met een lezers-schrijversvergrendeling
      Sta veel lezers of één schrijver tegelijk toe, bouw zo'n vergrendeling met semaforen en leg het probleem van uithongering en de kosten uit.
  3. Herken schendingen van atomiciteit en volgordeschendingen, de twee patronen achter vrijwel alle gelijktijdigheidsfouten zonder deadlock die in een onderzoek naar echte software zijn gevonden, en verhelp elk met het juiste hulpmiddel.
    • Zoek en verhelp schendingen van atomiciteit
      Herken code die ervan uitgaat dat een groep geheugentoegangen zonder onderbreking wordt uitgevoerd, laat de vervlechting zien die dit verstoort en bescherm de hele groep met één vergrendeling.
    • Zoek en verhelp volgordeschendingen
      Herken code die ervan uitgaat dat de ene toegang vóór de andere plaatsvindt, en dwing die volgorde af met een conditievariabele, een semafoor of wachten op threadbeëindiging in plaats van met timing.
  4. Leg uit hoe een deadlock uit vier voorwaarden ontstaat, voorkom die door één voorwaarde in het ontwerp uit te sluiten en beschrijf hoe systemen met voorkennis deadlocks vermijden of die detecteren en ervan herstellen.
    • Leg uit hoe een deadlock ontstaat
      Definieer deadlock, laat de vervlechting zien die die veroorzaakt, noem de vier noodzakelijke voorwaarden en gebruik een wachtafhankelijkheidsgraaf om te bepalen of threads in een deadlock zitten.
    • Voorkom deadlocks door een noodzakelijke voorwaarde uit te sluiten
      Ontwerp code zo dat nooit aan één van de vier voorwaarden kan worden voldaan: verwerf vergrendelingen in een vaste volgorde, verwerf alle vergrendelingen tegelijk, geef ze op en probeer het opnieuw met trylock, of vermijd vergrendelingen met atomaire instructies.
    • Vermijd deadlocks met planning en het bankiersalgoritme
      Gebruik voorkennis over wat threads kunnen aanvragen om alleen verzoeken toe te kennen die het systeem in een veilige toestand houden, en doorloop het bankiersalgoritme met concrete getallen.
    • Detecteer deadlocks en herstel de uitvoering
      Laat deadlocks ontstaan, vind ze door naar cycli te zoeken, herstel de uitvoering door een thread te beëindigen of terug te draaien en weeg dit af tegen preventie, vermijding en niets doen.
  5. Structureer een programma rond een gebeurtenislus die reageert op invoer en uitvoer die gereed zijn, voorkom blokkering met asynchrone I/O en opgeslagen toestand en weeg gebeurtenissen af tegen threads.
    • Voer een gebeurtenislus uit met select of poll
      Schrijf een gebeurtenislus met één thread die het besturingssysteem vraagt welke descriptoren gereed zijn, elke gebeurtenis volledig afhandelt en nooit blokkeert.
    • Voorkom blokkering met asynchrone I/O en opgeslagen toestand
      Dien I/O-verzoeken in zonder erop te wachten, bepaal wanneer ze zijn voltooid en bewaar gedurende die tijd de toestand die een afhandelaar nodig heeft.
    • Kies tussen gebeurtenissen en threads
      Vergelijk gebeurtenisgestuurde en threadgebaseerde ontwerpen op besturing, geheugen, gebruik van meerdere processorkernen en onderhoudbaarheid, en herken de fouten die in gebeurtenisgestuurde code blijven bestaan.

Deadlocks en semaforen: uitleg

5 hoofdstukken

1

Wacht tot de gedeelde toestand verandert

Een thread heeft vaak meer nodig dan exclusieve toegang: hij heeft een andere thread nodig die werk voltooit of gegevens beschikbaar maakt. Conditievariabelen laten hem tijdens dat wachten slapen. Hun correcte werking berust op een gedeelde toestandsvariabele, een mutex en een zorgvuldige overgang tussen controleren en slapen. Begin met een ouder die op een kind wacht en gebruik daarna hetzelfde patroon om producenten en consumenten te coördineren zonder CPU-tijd te verspillen of weksignalen te verliezen.

1.1 Toestand, een mutex en een plaats om te slapen

Een mutex beantwoordt de vraag “Wie mag nu bij deze gegevens?” Hij beantwoordt niet de vraag “Is het werk klaar?” Om op een gebeurtenis te wachten, heeft een thread een registratie van die gebeurtenis nodig en een manier om te slapen totdat die registratie verandert.

Waarom actief wachten een slechte manier van wachten is

De lus while (done == 0) ; besteedt CPU-tijd aan het herhaaldelijk controleren van een vlag. Op één CPU vertraagt dat werk het kind dat de vlag moet instellen. In C is het zonder synchronisatie lezen van een gewone variabele terwijl een andere thread erin schrijft bovendien een datarace. De compiler hoeft de vlag niet bij elke herhaling opnieuw te laden.

Een conditievariabele geeft threads een wachtrij. POSIX biedt pthread_cond_t, geïnitialiseerd met PTHREAD_COND_INITIALIZER of pthread_cond_init(). De wachtrij staat los van de toestand die wordt gecontroleerd: done legt voltooiing vast; de conditievariabele biedt een plaats om te slapen.

Houd de controle en het wachten bij elkaar

pthread_cond_wait(&c, &m) vereist dat de aanroeper mutex m vasthoudt. De aanroep geeft die mutex atomair vrij en begint te wachten. Voordat de aanroep terugkeert, verwerft de thread de mutex opnieuw. Dit sluit de gevaarlijke tussenruimte waarin een andere thread de toestand zou kunnen veranderen en signaleren voordat de wachtende thread daadwerkelijk is begonnen met wachten.

Hier wacht een ouder op de voltooiing van een kind. De namen lock, wait, signal en unlock verkorten de bijbehorende aanroepen van de threadbibliotheek.

/* Parent */
lock(&m);
while (done == 0)
    wait(&c, &m);
unlock(&m);

/* Child */
lock(&m);
done = 1;
signal(&c);
unlock(&m);

Aanvankelijk is done 0. Als de ouder eerst wordt uitgevoerd, vergrendelt die, ziet dat het werk niet klaar is en wacht. Het wachten geeft m vrij, zodat het kind die kan vergrendelen, done kan instellen en kan signaleren. De ouder verwerft uiteindelijk m opnieuw, controleert done opnieuw en gaat verder.

Als het kind eerst wordt uitgevoerd, legt het de voltooiing vast voordat de ouder arriveert. Het signaal wekt niemand, maar de ouder ziet later done == 1 en slaat het wachten over. De oplossing moet in beide volgordes correct werken.

Signalen slaan geen gebeurtenissen op

pthread_cond_signal(&c) deblokkeert ten minste één wachtende thread als die er is; POSIX staat toe dat er meer ontwaken. Zonder wachtende threads heeft de aanroep geen effect. pthread_cond_broadcast(&c) wekt alle op dat moment wachtende threads. Geen van beide aanroepen bewaart een melding voor toekomstige threads.

Als je done weglaat, werkt het patroon voor wachten op threadbeëindiging daarom niet: een kind kan signaleren voordat zijn ouder wacht, waarna de ouder gaat slapen zonder dat er nog een signaal volgt. Dit is een verloren weksignaal. done buiten de mutex controleren schept een andere mogelijkheid voor een verloren weksignaal: de ouder controleert en ziet dat de conditie onwaar is, het kind maakt die waar en signaleert, waarna de ouder begint te wachten.

Bescherm de toestandscontrole en elke toestandsverandering met dezelfde mutex. Houd die vast vanaf de controle tot en met de wachtoproep. Een eenvoudige signaleringsroute verandert de toestand en signaleert terwijl de thread die mutex nog vasthoudt. POSIX staat signaleren zonder de mutex vast te houden toe, maar het protocol voor de gedeelde toestand moet nog steeds een verloren weksignaal voorkomen.

Belangrijkste punten

  • De toestand legt de gebeurtenis vast; de conditievariabele biedt een wachtrij.
  • Roep de wachtbewerking aan met de mutex vastgehouden; de wachtbewerking geeft die atomair vrij en verwerft die opnieuw voordat ze terugkeert.
  • Bescherm de controle en de toestandsverandering met dezelfde mutex.
  • Een signaal dat wordt gestuurd terwijl niemand wacht, wordt niet bewaard.

1.2 Controleer opnieuw na het ontwaken

Terugkeren uit een wachtoproep betekent dat de thread het opnieuw mag proberen. Het betekent niet dat de conditie die de thread nodig heeft nog waar is. Dit onderscheid is van belang wanneer meerdere threads om dezelfde gegevens concurreren.

Mesa- en Hoare-semantiek
SemantiekWat een signaal de wachtende thread geeft
MesaGereedheid voor uitvoering; de wachtende thread moet de mutex opnieuw verwerven en de toestand opnieuw controleren.
HoareEen onmiddellijke overdracht van de vergrendeling en de uitvoering, waarbij de gesignaleerde conditie op dat moment behouden blijft.

POSIX, Java, C++-conditievariabelen en Python-condities volgen het Mesa-gedrag. Hoares monitors introduceerden het sterkere overdrachtsmodel; de aanpak van Mesa is de gebruikelijke praktische keuze.

Stel dat C1 wacht totdat een buffer met één plaats niet meer leeg is. Een producent vult die en signaleert C1. C1 is nu gereed, maar C2 kan eerst de mutex verwerven en het element pakken. Wanneer C1 uiteindelijk de mutex opnieuw verwerft, is de buffer weer leeg.

lock(&m);
while (count == 0)
    wait(&filled, &m);
v = get();
unlock(&m);

De while herhaalt de controle na elke terugkeer. Een if controleert alleen vóór de eerste wachtoproep en zou C1 get() op een lege buffer laten aanroepen. Gebruik ook met één producent en één consument while: POSIX staat ook spontaan ontwaken toe, waarbij de wachtoproep terugkeert zonder bijbehorend signaal.

Eén thread wekken of allemaal?

Een signaal is geschikt als de wachtende threads dezelfde conditie nodig hebben en elk van hen de verandering kan benutten. Een broadcast is nuttig als meerdere threads verder kunnen, of als verschillende wachtende threads verschillende hoeveelheden nodig hebben en de signalerende thread niet de juiste kan selecteren.

Neem een allocator met 0 vrije bytes. Ta heeft 100 bytes nodig en Tb 10. Een andere thread geeft 50 bytes vrij. Een signaal kan Ta wekken, die nog steeds niet verder kan, terwijl Tb blijft slapen hoewel zijn verzoek past. Een broadcast wekt beide. Ze verwerven de mutex één voor één opnieuw; Tb kan geheugen toegewezen krijgen, terwijl Ta opnieuw wacht. Dit is een overkoepelende conditie: de melding bereikt elke wachtende thread die mogelijk verder kan.

Een broadcast kan extra weksignalen, contextwisselingen en concurrentie om de mutex veroorzaken. Afzonderlijke conditievariabelen voor verschillende predicaten kunnen signalering selectiever maken. In een buffer moeten producenten en consumenten niet één ongedifferentieerde signaalwachtrij delen.

Het verband met monitors

Een monitor bundelt gedeelde gegevens met bewerkingen die één thread tegelijk toelaten en met conditievariabelen om te wachten. Java-objecten bieden een vergrendeling en een wachtverzameling. Binnen synchronized kan een thread wait(), notify() of notifyAll() aanroepen; aanroepen zonder eigenaar van de monitor van dat object te zijn, werpt IllegalMonitorStateException.

Wachten geeft alleen de bijbehorende mutex of de vergrendeling van het Java-object vrij. Als een thread ook een andere vergrendeling vasthoudt, blijft die tweede vergrendeling vastgehouden. Een producent die die nodig heeft, kan de conditie dan mogelijk niet waar maken. Controleer bij een wachtoproep alle vastgehouden vergrendelingen, niet alleen de vergrendeling die in de aanroep staat.

Belangrijkste punten

  • Bij Mesa-semantiek maakt ontwaken een thread gereed; die moet nog steeds concurreren om de mutex.
  • Wacht in een while-lus om veranderingen in de toestand en spontaan ontwaken op te vangen.
  • Wek alle wachtende threads als één signaal een thread die verder kan in slaap zou kunnen laten.
  • Afzonderlijke wachtcondities kunnen afzonderlijke conditievariabelen gebruiken.
  • Wachten geeft alleen de bijbehorende vergrendeling vrij.

1.3 Bouw een begrensde buffer met twee wachtrijen

Producenten voegen elementen toe aan een buffer met vaste grootte; consumenten halen ze eruit. Een correcte begrensde buffer voorkomt dat een volle buffer wordt overschreven, dat elementen uit een lege buffer worden gehaald en dat de administratie gelijktijdig wordt gewijzigd. De verzoekenwachtrij van een webserver en de pipe tussen grep error log.txt en wc -l passen beide in dit patroon.

Volg de ringindices

Een ringbuffer gebruikt een array en twee indices. fill_ptr geeft de volgende schrijfplaats aan; use_ptr geeft de volgende leesplaats aan. Beide worden verhoogd modulo MAX. Een afzonderlijke count maakt onderscheid tussen leeg en vol, ook als de twee indices gelijk zijn.

void put(int v) {
    buffer[fill_ptr] = v;
    fill_ptr = (fill_ptr + 1) % MAX;
    count++;
}
int get(void) {
    int v = buffer[use_ptr];
    use_ptr = (use_ptr + 1) % MAX;
    count--;
    return v;
}

Begin met capaciteit 3 en beide indices op 0. Het plaatsen van 7, 8 en 9 vult de plaatsen 0, 1 en 2. De schrijfindex springt terug naar 0 en count bereikt 3. Ophalen geeft 7 terug en verhoogt de leesindex naar 1. Het plaatsen van 10 gebruikt plaats 0 opnieuw. De array is nu [10, 8, 9], maar verwijderen volgens FIFO volgt de leesindex: eerst komt 8, dan 9 en dan 10.

Richt elk signaal op de andere rol

Gebruik één mutex, empty voor producenten die op ruimte wachten en fill voor consumenten die op een element wachten. Initialiseer beide conditievariabelen en de mutex voordat je threads start.

/* Producer: repeat for each item v */
lock(&m);
while (count == MAX)
    wait(&empty, &m);
put(v);
signal(&fill);
unlock(&m);

/* Consumer */
lock(&m);
while (count == 0)
    wait(&fill, &m);
v = get();
signal(&empty);
unlock(&m);

De controle en de wijziging van de buffer blijven in één kritieke sectie. Anders zouden twee producenten allebei één vrije plaats kunnen zien en elkaars werk overschrijven. Wachten geeft m vrij, zodat de andere rol de buffer kan veranderen.

Waarom één wachtrij kan vastlopen

Met één conditievariabele en signalen kunnen producenten en consumenten de verkeerde rol wekken. Bij capaciteit 1 slapen C1 en C2 eerst omdat de buffer leeg is. P voegt een element in, wekt C1 en wacht daarna omdat de buffer vol is. C1 haalt het element eruit en signaleert, maar het signaal kan C2 wekken in plaats van P. C1 wacht opnieuw; C2 ziet ook dat de buffer leeg is en wacht. P blijft slapen hoewel er ruimte beschikbaar is. Twee wachtrijen leiden elk signaal naar de rol die nu verder kan. Alle wachtende threads op een gedeelde wachtrij wekken kan dat selectieprobleem voorkomen, ten koste van extra weksignalen.

Een grotere capaciteit vangt pieken op en laat productie en consumptie buiten de korte kritieke sectie van de buffer overlappen. De wijzigingen aan de buffer zelf blijven na elkaar plaatsvinden. Capaciteit 1 dwingt elke invoeging en verwijdering elkaar af te wisselen.

Voor een nette afsluiting wacht je eerst tot elke producent klaar is met zijn echte elementen. Plaats daarna per consument één gereserveerd gegevenseindelement in de wachtrij, zoals -1 als echte elementen niet-negatief zijn. Elke consument stopt nadat hij er één heeft gepakt. Als je die markeringen verstuurt terwijl producenten nog actief zijn, kunnen echte elementen zonder consument achterblijven.

Belangrijkste punten

  • Verhoog ringindices modulo de capaciteit; de teller maakt onderscheid tussen leeg en vol.
  • Producenten wachten op ruimte en signaleren elementen; consumenten wachten op elementen en signaleren ruimte.
  • Gebruik while-lussen en één mutex rond het predicaat en de wijziging van de buffer.
  • Eén gedeelde signaalwachtrij kan de verkeerde rol wekken en alle threads laten slapen.
  • Wacht tot alle producenten klaar zijn voordat je per consument één eindmarkering in de wachtrij plaatst.
  • De FIFO-volgorde volgt de leesindex, niet de fysieke volgorde in de array.
2

Tel toegangsbewijzen met semaforen

Een semafoor registreert toegangsbewijzen die threads verwerven en teruggeven. Dankzij de beginwaarde kan dezelfde primitieve bouwsteen exclusieve toegang afdwingen, een gebeurtenis onthouden of een verzameling bronnen begrenzen. Volg hoe de waarde verandert door wacht- en vrijgavebewerkingen en combineer daarna aantallen toegangsbewijzen met een mutex om een begrensde buffer te bouwen. Een lezers-schrijversontwerp voegt een andere toepassing toe: lezers delen de toegang terwijl schrijvers exclusieve toegang krijgen, met gevolgen voor de eerlijkheid die je gaat onderzoeken.

2.1 Kies de beginwaarde van een semafoor

Een semafoor bevat een geheel getal dat door atomaire wacht- en vrijgavebewerkingen wordt veranderd. Dijkstra introduceerde die in de jaren 1960 als P en V; POSIX noemt de bewerkingen sem_wait() en sem_post(). Atomair betekent dat twee threads dezelfde tellerbewerking niet tegelijk gedeeltelijk kunnen uitvoeren.

Beschikbare eenheden bepalen de beginwaarde

sem_init(&s, 0, X) maakt een naamloze semafoor met waarde X. Het tweede argument 0 kiest voor delen tussen threads in één proces. Voor delen tussen processen vereist een argument ongelijk aan nul dat de semafoor zich in gedeeld geheugen bevindt.

DoelBeginwaardeBetekenis
Vergrendeling1Eén thread mag meteen naar binnen.
Volgorde van gebeurtenissen0De gebeurtenis heeft nog niet plaatsgevonden.
Verzameling bronnenNN eenheden kunnen nu worden verworven.

Een binaire semafoor die als vergrendeling wordt gebruikt, plaatst een wachtbewerking vóór en een vrijgavebewerking na een kritieke sectie. Als die bij 0 begint, wordt de eerste thread die naar binnen wil geblokkeerd. Als die bij 2 begint, kunnen twee threads naar binnen en is er geen wederzijdse uitsluiting.

Twee equivalente telmodellen

In Dijkstra's definitie blijft de waarde niet-negatief: de wachtbewerking slaapt tot de waarde kan worden verlaagd zonder onder nul te komen en de vrijgavebewerking voegt een eenheid toe. Linux volgt deze niet-negatieve conventie.

Gebruik voor handmatige uitvoeringsverlopen het model met een teller met teken: de wachtbewerking verlaagt de waarde en blokkeert als de nieuwe waarde negatief is; de vrijgavebewerking verhoogt de waarde en wekt een wachtende thread als die is geblokkeerd. In dit model telt de absolute waarde van een negatieve waarde de geblokkeerde wachtende threads. Het is een redeneerconventie, geen negatieve waarde die je van de Linux-semafoor-API moet verwachten.

Volg een vergrendeling die op 1 is geïnitialiseerd:

  1. T0 wacht: 1 wordt 0, dus T0 gaat naar binnen.
  2. T1 wacht: 0 wordt −1, dus T1 blokkeert.
  3. T0 geeft vrij: −1 wordt 0, zodat T1 verder kan.
  4. T1 is klaar en geeft vrij: 0 wordt 1.

Het weksignaal draagt het bestaande toegangsbewijs over aan T1; er is geen nieuwe wachtbewerking nodig voordat T1 zijn kritieke sectie binnengaat.

Een vrijgavebewerking wordt onthouden

Als niemand wacht, verhoogt een vrijgavebewerking het beschikbare aantal. Een latere wachtbewerking kan dat toegangsbewijs gebruiken. Dit is nuttig als één thread klaar is voordat de thread die erop wacht is begonnen. Een signaal van een conditievariabele heeft zo'n geheugen niet.

Semaforen hebben geen eigenaar: één thread mag wachten en een andere mag een vrijgavebewerking uitvoeren. Een mutex behoort toe aan de thread die die heeft vergrendeld; een standaard-POSIX-mutex vanuit een andere thread ontgrendelen is ongedefinieerd gedrag. De semafoor zonder eigenaar maakt signalering mogelijk, maar maakt een extra vrijgavebewerking ook lastiger te diagnosticeren.

Een semafoor kan worden opgebouwd uit een mutex, een conditievariabele en een niet-negatief geheel getal. De wachtbewerking vergrendelt, herhaalt zolang value <= 0, wacht, verlaagt de waarde en ontgrendelt. De vrijgavebewerking vergrendelt, verhoogt de waarde, signaleert en ontgrendelt. De mutex maakt de tellerbewerkingen atomair; de lus vangt weksignalen op waarna geen eenheid beschikbaar blijft. Vergrendelingen met conditievariabelen en semaforen kunnen elkaar implementeren, al is een correcte implementatie van conditievariabelen met semaforen ingewikkelder.

Belangrijkste punten

  • Initialiseer een semafoor op het aantal eenheden dat direct beschikbaar is.
  • Een vergrendelingssemafoor begint bij 1 en combineert een wachtbewerking vóór de sectie met een vrijgavebewerking erna.
  • Het model met een teller met teken gebruikt negatieve waarden voor geblokkeerde wachtende threads; Linux-waarden blijven niet-negatief.
  • Vrijgavebewerkingen worden als toegangsbewijzen onthouden, ook als er op dat moment niemand wacht.
  • Een semafoor heeft geen eigenaar; een mutex wel.

2.2 Onthoud gebeurtenissen en begrens het gebruik van bronnen

Hetzelfde paar van een wacht- en een vrijgavebewerking kan een gebeurtenis of een beperkte voorraad voorstellen. Bepaal wat een toegangsbewijs betekent voordat je het beginaantal kiest en bepaalt waar elke bewerking hoort.

Laat voltooiing aan gebruik voorafgaan

Initialiseer finished op 0 voor een ouder die op een kind wacht. Het kind voert een vrijgavebewerking uit nadat het zijn werk heeft voltooid. De ouder wacht voordat die het resultaat gebruikt.

/* Child */
produce_result();
sem_post(&finished);

/* Parent */
sem_wait(&finished);
use_result();

Als de ouder eerst arriveert, verandert de waarde met teken van 0 naar −1 en gaat de ouder slapen. De vrijgavebewerking van het kind brengt de waarde terug naar 0 en wekt de ouder. Als het kind eerst arriveert, verandert zijn vrijgavebewerking 0 in 1; de latere wachtbewerking verandert 1 in 0 zonder te blokkeren. Eén voltooide gebeurtenis maakt één succesvolle wachtbewerking mogelijk.

Tel een verzameling bronnen

Een verzameling van vier databaseverbindingen gebruikt een semafoor die op 4 is geïnitialiseerd. Elke client wacht voordat die een verbinding leent en voert een vrijgavebewerking uit nadat die de verbinding heeft teruggegeven. Een vijfde gelijktijdige gebruiker gaat slapen. De semafoor telt de toelatingen; een afzonderlijke mutex beschermt nog steeds de gedeelde lijst die aangeeft welke verbinding vrij is. Twee toegelaten threads mogen niet dezelfde lijstvermelding verwijderen.

Begrenzing van gelijktijdige toegang past hetzelfde idee toe op een kostbare fase. Stel dat 100 threads tijdens de verwerking elk 500 MB nodig hebben. Samen zouden ze 50.000 MB nodig kunnen hebben. Een machine met 8 GB heeft volgens decimale SI-eenheden 8.000 MB. Als je hoogstens 12 werkthreads toelaat, beperk je deze fase tot 6.000 MB en blijft 2.000 MB over voor ander gebruik.

sem_wait(&processing_slots);
run_memory_heavy_phase();
sem_post(&processing_slots);

Elk pad na een succesvolle wachtbewerking moet het toegangsbewijs teruggeven, ook een foutpad. Een vrijgavebewerking vergeten vermindert geleidelijk de capaciteit. Twee keer een vrijgavebewerking uitvoeren creëert een extra toegangsbewijs en laat meer gelijktijdige gebruikers toe dan bedoeld.

Ontmoet elkaar bij een rendez-vous

Een rendez-vous vereist dat twee threads arriveren voordat een van beide verdergaat. Initialiseer a_arrived en b_arrived op 0.

/* A */
sem_post(&a_arrived);
sem_wait(&b_arrived);

/* B */
sem_post(&b_arrived);
sem_wait(&a_arrived);

Als A eerst arriveert, legt die zijn aankomst vast en blokkeert voor B. B legt zijn aankomst vast en kan het bewaarde toegangsbewijs van A gebruiken. A kan daarna het toegangsbewijs van B gebruiken. Als je beide reeksen omdraait zodat ze vóór de vrijgavebewerking wachten, gaat elke thread slapen voordat die iets heeft aangekondigd.

Deze twee semaforen implementeren één ontmoeting. Om de synchronisatie in herhaalde rondes opnieuw te gebruiken, moet je voorkomen dat rondes elkaars aankomsten gebruiken; één succesvol verloop is geen herbruikbaar barrièreontwerp.

Een semafoor past goed bij een aantal of een eenvoudige gebeurtenis. Een conditievariabele past bij een rijker predicaat, zoals wachten tot een wachtrij meerdere elementen van een vereist type bevat. In dat geval beschermt de mutex de toestand waaruit de conditie wordt berekend.

Belangrijkste punten

  • Laat een semafoor voor de uitvoeringsvolgorde bij 0 beginnen en voer pas een vrijgavebewerking uit na de gebeurtenis die die voorstelt.
  • Een telsemafoor begrenst de toelating, maar beschermt de lijst met bronnen niet.
  • Begrenzing van gelijktijdige toegang beperkt de gelijktijdige vraag naar geheugen of een andere beperkte bron.
  • Kondig bij een rendez-vous tussen twee threads je aankomst aan voordat je op de andere thread wacht.
  • Een extra vrijgavebewerking creëert capaciteit; een ontbrekende vrijgavebewerking neemt capaciteit weg.

2.3 Reserveer een plaats voordat je de buffer vergrendelt

Een buffer met semaforen onderscheidt twee vragen: “Is er een element of plaats beschikbaar?” en “Wie mag de array nu wijzigen?” Twee telsemaforen beantwoorden de eerste vraag en een mutex beantwoordt de tweede.

Stel drie afzonderlijke besturingselementen in

Initialiseer bij capaciteit MAX de semafoor empty op MAX, full op 0 en de mutex als ontgrendeld. Hier zijn empty en full semaforen, geen conditievariabelen. Een toegangsbewijs van empty reserveert één plaats; een toegangsbewijs van full reserveert één element.

/* Producer */
sem_wait(&empty);
lock(&mutex);
put(v);
unlock(&mutex);
sem_post(&full);

/* Consumer */
sem_wait(&full);
lock(&mutex);
v = get();
unlock(&mutex);
sem_post(&empty);

De producent verwerft ruimte voordat hij de ring aanspreekt en maakt daarna het voltooide element beschikbaar met een vrijgavebewerking op full. De consument verwerft een element, verwijdert het onder de mutex en geeft ruimte terug met een vrijgavebewerking op empty. Een succesvolle wachtbewerking op een semafoor reserveert de bron al, dus deze aanroepen hebben geen predicaatlus nodig zoals bij een conditievariabele.

Volg één plaats

Aanvankelijk is empty 1 en full 0. Een consument wacht op full, waardoor die in het model met een teller met teken −1 wordt, en gaat slapen. De producent wacht op empty, waardoor die van 1 naar 0 gaat. De producent vergrendelt, voegt een element in, ontgrendelt en voert een vrijgavebewerking op full uit, waardoor −1 in 0 verandert en de consument verder kan.

De volgende wachtbewerking van de producent op empty verandert 0 in −1 en blokkeert. De consument neemt het element onder de mutex en voert een vrijgavebewerking op empty uit, waardoor −1 in 0 verandert en de producent verder kan. De semafoorwaarden regelen de overdracht; voor de synchronisatie is geen afzonderlijke count nodig.

Bij een grotere buffer kunnen meerdere producenten de ruimtesemafoor passeren. Zonder de mutex zouden ze dezelfde fill_ptr kunnen lezen en naar dezelfde plaats kunnen schrijven. Een reservering is geen exclusieve toegang tot de indices van de array.

Waarom eerst vergrendelen een deadlock kan veroorzaken

Stel dat de consument mutex vergrendelt voordat hij op full wacht. Bij een lege buffer slaapt hij terwijl hij de mutex nog vasthoudt. De producent heeft die mutex nodig om het element in te voegen waarvan de vrijgavebewerking de consument zou laten doorgaan. Geen van beide kan zijn werk afmaken.

Een wachtbewerking op een semafoor geeft geen mutex vrij. Een wachtbewerking op een conditievariabele geeft bewust de bijbehorende mutex vrij. De volgorde is bij deze buffer dus van belang: eerst reserveren, dan vergrendelen, de buffer veranderen, ontgrendelen en vervolgens het toegangsbewijs voor de andere rol beschikbaar maken.

De bredere regel is dat je niet moet blokkeren terwijl je een vergrendeling vasthoudt die nodig is voor de bewerking die je kan deblokkeren. Die regel verklaart waarom het lezers-schrijversalgoritme een andere veilige inrichting kan hebben: de schrijver heeft de vergrendeling van de lezersteller niet nodig om zijn bron vrij te geven.

De mutex kan zelf een binaire semafoor zijn die op 1 is geïnitialiseerd. Zijn rol blijft wederzijdse uitsluiting; de andere twee semaforen tellen bronnen.

Belangrijkste punten

  • Initialiseer empty op de capaciteit, full op 0 en de mutex als ontgrendeld.
  • Reserveer ruimte of een element voordat je de buffermutex verwerft.
  • Bescherm ringindices en wijzigingen aan de array ook als de toegangsbewijzen correct worden geteld.
  • Wachten op een semafoor geeft geen mutex vrij.
  • Voer na het invoegen een vrijgavebewerking op full uit en na het verwijderen op empty.
  • Een geblokkeerde wachtende thread mag geen vergrendeling vasthouden die de thread nodig heeft die hem kan deblokkeren.

2.4 Laat lezers delen en schrijvers alleen werken

Structuren die vaak worden gelezen, kunnen meerdere lezers tegelijk toelaten omdat lezen de gegevens niet verandert. Een lezers-schrijversvergrendeling laat een groep lezers of één schrijver toe. Elke wijziging van de beschermde gegevens vereist schrijftoegang.

Bouw de groep lezers op

Gebruik readers = 0, een semafoor lock die op 1 is geïnitialiseerd om die teller te beschermen, en writelock die op 1 is geïnitialiseerd om de gedeelde bron te beschermen.

/* Acquire read access */
sem_wait(&lock);
readers++;
if (readers == 1)
    sem_wait(&writelock);
sem_post(&lock);

/* Release read access */
sem_wait(&lock);
readers--;
if (readers == 0)
    sem_post(&writelock);
sem_post(&lock);

Een schrijver wacht op writelock, verandert de gegevens en voert daarna een vrijgavebewerking op writelock uit. Hij verwerft de vergrendeling van de teller niet. De eerste lezer verwerft writelock voor de groep en de laatste lezer geeft die terug. Een semafoor is geschikt voor die overdracht, omdat de thread die de vrijgavebewerking uitvoert niet de thread hoeft te zijn die oorspronkelijk heeft gewacht.

Volg hoe R1 naar binnen gaat: R1 verhoogt readers naar 1 en verwerft writelock. R2 verhoogt de teller naar 2 zonder nog een toegangsbewijs te verwerven. Schrijver W blokkeert nu op writelock. R1 vertrekt en verlaagt de teller naar 1. R2 vertrekt, verlaagt de teller naar 0 en voert een vrijgavebewerking uit, zodat W als enige kan schrijven.

De eerste lezer kan op writelock wachten terwijl hij lock vasthoudt. Dit veroorzaakt niet de deadlock uit de begrensde buffer: de huidige schrijver heeft de vergrendeling van de teller niet nodig om zijn werk af te maken en een vrijgavebewerking op writelock uit te voeren.

Eerlijkheid is een afzonderlijke eigenschap

Dit ontwerp geeft lezers voorrang. Zolang de lezersteller positief blijft, kan een andere lezer zich bij de groep voegen vóór een wachtende schrijver. Een voortdurende stroom overlappende lezers kan de schrijver onbeperkt laten wachten: uithongering van schrijvers.

BeleidToelatingskeuzeRisico
LezersvoorrangNieuwe lezers mogen zich bij actieve lezers voegen.Schrijvers kunnen worden uitgehongerd.
SchrijversvoorrangWachtende schrijvers verhinderen dat nieuwe lezers binnenkomen.Lezers kunnen worden uitgehongerd.
Eerlijke toelatingBinnenkomende threads krijgen toegang in een eerlijke volgorde.Meer administratie voor de toelating.

Het basisontwerp biedt veiligheid, maar geen grens aan hoe lang een schrijver wacht. Een hogere doorvoer van lezers is niet hetzelfde als een eerlijke bediening.

Gebruik de juiste modus

Een leesvergrendeling kan een schrijfbewerking niet tegen andere lezers beschermen. Zelfs het wijzigen van een cacheteller onder leestoegang kan een race met een andere lezer veroorzaken. Om in dit basisontwerp over te stappen op schrijftoegang, geef je de leestoegang vrij, verwerf je schrijftoegang en controleer je de conditie opnieuw. Als een thread op schrijftoegang wacht terwijl hij nog als lezer meetelt, kan hij voor altijd op zichzelf wachten.

POSIX biedt pthread_rwlock_t, pthread_rwlock_rdlock(), pthread_rwlock_wrlock() en pthread_rwlock_unlock(). Java biedt ReentrantReadWriteLock; C++17 biedt std::shared_mutex. Deze vergrendelingen vergen meer administratie dan een gewone mutex. Meet de werklast: bij korte secties of veel schrijfbewerkingen kan een eenvoudige mutex even snel of sneller zijn.

Belangrijkste punten

  • Een lezers-schrijversvergrendeling laat veel lezers of één exclusieve schrijver toe.
  • De eerste lezer verwerft het toegangsbewijs voor de groep; de laatste lezer geeft het terug.
  • De lezersteller heeft een eigen vergrendeling nodig.
  • Lezersvoorrang kan een wachtende schrijver uithongeren.
  • Geef leestoegang vrij voordat je schrijftoegang aanvraagt en controleer daarna de gegevens opnieuw.
3

Zoek de aanname over de volgorde achter een fout

Bij veel gelijktijdigheidsfouten blijft het programma met verkeerde gegevens draaien. Twee terugkerende oorzaken zijn meerdere toegangen behandelen als één ondeelbare handeling en aannemen dat het werk van de ene thread voorafgaat aan dat van een andere. Door enkele zorgvuldig gekozen toegangen te volgen, wordt de verborgen aanname vaak zichtbaar. De oplossing hangt van die aanname af: bescherm een volledige bewerking als die bijeen moet blijven en leg een expliciete afhankelijkheid vast als de ene bewerking eerst moet plaatsvinden.

3.1 Bescherm de volledige controle en handeling

Een schending van atomiciteit ontstaat wanneer code erop vertrouwt dat een groep toegangen ondeelbaar is, maar een andere thread ertussen kan handelen. Elke afzonderlijke toegang kan redelijk lijken terwijl de gecombineerde bewerking faalt.

Zoek de tussenruimte na de controle

Bekijk een gedeelde logbuffer:

/* T1 */
if (log->buf != NULL)
    write_out(log->buf);

/* T2 */
log->buf = NULL;

T1 kan een niet-nulle pointer controleren, pauzeren en verdergaan nadat T2 die op null heeft gezet. T1 geeft dan null door aan write_out(). De bedoelde garantie was niet alleen een geldige controle, maar ook dat de gecontroleerde waarde tijdens de aanroep bruikbaar bleef.

Bescherm de controle en het gebruik samen en bescherm de concurrerende toekenning met dezelfde mutex:

/* T1 */
lock(&m);
if (log->buf != NULL)
    write_out(log->buf);
unlock(&m);

/* T2 */
lock(&m);
log->buf = NULL;
unlock(&m);

T2 wordt nu vóór of na de volledige bewerking van T1 uitgevoerd. Elke thread een eigen vergrendeling geven helpt niet: beide kunnen hun afzonderlijke vergrendeling tegelijk vasthouden. Als je alleen T1 vergrendelt, kan T2 nog steeds in de tussenruimte handelen.

Een geldige controle kan toch tot een ongeldig saldo leiden

Begin met saldo 50. Twee threads willen elk 30 opnemen, maar alleen als er genoeg overblijft. Beide kunnen 50 lezen en balance >= 30 passeren voordat een van beide handelt. Als de opnames daarna worden toegepast, kan het saldo −10 worden. Als je de controle en aftrekking samen beschermt, laat de eerste opname 20 over; de volgende thread ziet 20 en weigert.

Zelfs atomaire laad- en aftrekbewerkingen maken van dat paar nog geen transactie. Een andere thread kan tussen de atomaire controle en de atomaire verandering handelen. Eén vergrendeling rond de volledige beslissing en verandering dwingt de vereiste afbakening af.

Hetzelfde probleem zie je bij count++: laden, optellen en opslaan zijn afzonderlijke stappen. Twee threads kunnen dezelfde oude waarde laden en dezelfde verhoogde waarde opslaan, waardoor een wijziging verloren gaat. Een vergrendeling rond de verhoging of een atomaire fetch-and-add voert de vereiste wijziging veilig uit.

Gebruik onderzoeksresultaten zonder te veralgemeniseren

Lu en collega's onderzochten in een onderzoek uit 2008 105 verholpen gelijktijdigheidsfouten in MySQL, Apache, Mozilla en OpenOffice. Daarvan waren er 74 fouten zonder deadlock en 31 deadlocks. Schendingen van atomiciteit en volgordeschendingen vormden 97% van de steekproef zonder deadlock. Dit zijn bevindingen over die onderzochte steekproef, geen percentage dat voor elke codebasis gegarandeerd is.

Ongeveer 92% van de onderzochte fouten kon worden uitgelokt door de volgorde van hoogstens vier geheugentoegangen te beheersen. Een kleine tabel met een vervlechting kan daardoor meer inzicht geven dan duizenden succesvolle gewone uitvoeringen. ThreadSanitizer helpt uitgevoerde dataraces te vinden, maar een logische atomiciteitsfout kan blijven bestaan ook als elke afzonderlijke toegang is gesynchroniseerd. Test de invariant van de hele bewerking, zoals “een succesvolle opname maakt het saldo nooit negatief”.

Belangrijkste punten

  • Kies de kritieke sectie rond de hele bewerking die ondeelbaar moet zijn.
  • Elke concurrerende toegang moet hetzelfde vergrendelingsprotocol volgen.
  • Atomaire afzonderlijke toegangen kunnen nog steeds een tussenruimte tussen controle en handeling laten bestaan.
  • Volg een kleine schadelijke vervlechting in plaats van alleen op herhaalde succesvolle uitvoeringen te vertrouwen.

3.2 Laat de ene bewerking vóór de andere plaatsvinden

Een volgordeschending betekent dat bewerking A vóór bewerking B moet plaatsvinden, maar dat het programma geen synchronisatie biedt die dit garandeert. Wederzijdse uitsluiting alleen kan niet bepalen welke bewerking eerst gaat.

Een thread kan direct worden uitgevoerd

Stel dat een aanmakende thread een nieuw aangemaakt werkthreadrecord in een gedeelde pointer opslaat, terwijl de werkthread begint met het gebruiken van die pointer:

/* Creator */
worker = thread_create(worker_main);

/* New worker */
use(worker->state);

De werkthread kan worden uitgevoerd voordat thread_create() terugkeert en voordat de toekenning is voltooid. De volgorde in de broncode van de aanmakende thread zegt niets over wanneer de nieuwe thread CPU-tijd krijgt. Een succesvolle uitvoering betekent alleen dat de aanmakende thread die race toevallig heeft gewonnen.

Maak gereedheid na initialisatie bekend

Voeg ready = 0, mutex m en conditievariabele c toe. De aanmakende thread houdt de mutex vast terwijl die de pointer toekent en gereedheid bekendmaakt:

/* Creator */
lock(&m);
worker = thread_create(worker_main);
ready = 1;
signal(&c);
unlock(&m);

/* Worker, before using worker */
lock(&m);
while (ready == 0)
    wait(&c, &m);
unlock(&m);
use(worker->state);

Als de werkthread de mutex eerst bereikt, kan die de gereedheidscontrole niet passeren voordat de aanmakende thread het record beschikbaar heeft gemaakt. Als de werkthread later arriveert, onthoudt ready dat de initialisatie klaar is. Houd het record in leven zolang de werkthread het gebruikt en synchroniseer eventuele latere wijzigingen.

Een semafoor die op 0 is geïnitialiseerd, biedt een kortere overdracht: de aanmakende thread voert na de toekenning van de pointer een vrijgavebewerking uit en de werkthread wacht voordat die de pointer gebruikt. Voor een resultaat dat pas bestaat nadat een thread klaar is, legt wachten op de beëindiging van die thread vóór het lezen van het resultaat de vereiste volgorde vast. Het vrijgeven van een object moet eveneens wachten tot de gebruikers klaar zijn, bijvoorbeeld door op threadbeëindiging te wachten of met een correct gesynchroniseerde referentieteller.

Waarom veranderingen in timing het probleem niet oplossen

Het toevoegen van sleep(1) of sched_yield() verandert alleen de kans op de gewenste uitvoeringsvolgorde. Een trager of belast systeem kan de consument nog steeds te vroeg laten doorgaan. Een mutex rond elke bewerking voorkomt overlap, maar laat nog steeds toe dat het gebruik de mutex vóór de initialisatie verwerft.

Verborgen aannameSoort foutVereiste garantie
“Er verandert niets tussen deze controle en handeling.”Schending van atomiciteitEén beschermde bewerking.
“De initialisatie is klaar vóór dit gebruik.”VolgordeschendingEen expliciete afhankelijkheid van voltooiing of gereedheid.

In het onderzoek van Lu en collega's was ongeveer een derde van de fouten zonder deadlock een volgordeschending en werd ongeveer 73% van de fouten zonder deadlock niet opgelost door alleen vergrendelingen toe te voegen of te veranderen. Benoem voordat je een primitieve bouwsteen kiest de bewerking die klaar moet zijn en de bewerking die moet wachten. Die afhankelijkheid bepaalt waar de vrijgavebewerking, het signaal of het wachten op threadbeëindiging hoort.

Belangrijkste punten

  • Een volgordeschending neemt aan dat een eerdere handeling is voltooid zonder die volgorde af te dwingen.
  • Maak gereedheid pas na initialisatie bekend en wacht voordat je het resultaat gebruikt.
  • Een vergrendeling voorkomt overlap, maar bepaalt niet wie eerst gaat.
  • Slapen en de CPU-beurt afstaan veranderen de timing zonder een afhankelijkheid te garanderen.
  • Houd gedeelde objecten in leven totdat alle gebruikers klaar zijn.
4

Redeneer over deadlocks en voortgang

Een deadlock verandert een verzameling gewone wachtmomenten in een permanente stilstand: elke thread heeft iets nodig wat een andere geblokkeerde thread vasthoudt. Een afhankelijkheidsgraaf maakt die cyclus zichtbaar. De vier noodzakelijke voorwaarden wijzen op manieren om die te voorkomen; vooraf opgegeven bronbehoeften maken vermijding mogelijk; en een detector kan een deadlock vinden nadat die is ontstaan. Vergelijk deze strategieën op de kennis die ze vereisen, de gelijktijdigheid die ze kosten en het werk dat bij herstel verloren kan gaan.

4.1 Volg de cyclus van wachtende threads

Een verzameling threads zit in een deadlock wanneer elke thread wacht op iets wat alleen een andere thread in de verzameling kan bieden. Geen van de threads kan verdergaan en de handeling uitvoeren die de andere laat doorgaan. Het programma kan bij veel uitvoeringsvolgordes werken voordat één ongelukkige volgorde de cyclus blootlegt.

Bouw de fout met twee vergrendelingen op

T1 verwerft L1 en vraagt daarna L2 aan. T2 verwerft L2 en vraagt daarna L1 aan. Als T1 L1 verwerft en daarna T2 L2 verwerft, blokkeren beide tweede verwervingen. Elke thread houdt precies de bron vast die de andere nodig heeft.

Dit kan in gewone functies verborgen zitten. Als transfer(a, b) eerst a en daarna b vergrendelt, kunnen gelijktijdige aanroepen transfer(x, y) en transfer(y, x) de rekeningvergrendelingen in omgekeerde volgorde verwerven. Inkapseling maakt een aanroep gemakkelijk, maar kan de vergrendelingen die de aanroep verwerft verbergen.

Herken alle vier de voorwaarden
Noodzakelijke voorwaardeBetekenis in het voorbeeld met vergrendelingen
Wederzijdse uitsluitingSlechts één thread houdt een bepaalde vergrendeling vast.
Vasthouden en wachtenElke thread houdt één vergrendeling vast terwijl hij op een andere wacht.
Geen gedwongen ontnemingEen vastgehouden vergrendeling wordt niet gedwongen afgenomen.
Circulair wachtenElke thread wacht op de vergrendeling die de volgende thread vasthoudt.

Alle vier de voorwaarden zijn noodzakelijk. Als je één ervan in het ontwerp uitsluit, voorkom je een deadlock om bronnen. De aanwezigheid van exclusieve vergrendelingen en geneste verwerving alleen bewijst niet dat een bepaalde uitvoering een cyclus heeft bereikt.

Lees een wachtafhankelijkheidsgraaf

Teken per thread een knoop. Teken T → U wanneer T wacht op een bron die U vasthoudt. Bij bronnen met één exemplaar, zoals exclusieve vergrendelingen, wijst een cyclus op een deadlock.

Als T1 A vasthoudt en op B wacht, T2 B vasthoudt en op C wacht, en T3 C vasthoudt en op A wacht, is de graaf T1 → T2 → T3 → T1. Alle drie zitten vast. Als T3 in plaats daarvan een vrije vergrendeling D aanvraagt, kan T3 zijn werk afmaken en C vrijgeven, zodat T2 en daarna T1 verder kunnen. Een keten is geen cyclus.

Als brontypen meerdere uitwisselbare exemplaren hebben, is een cyclus alleen onvoldoende: een extra exemplaar kan beschikbaar komen. Gebruik aantallen bronnen en openstaande verzoeken om dat geval te beoordelen.

Dinerende filosofen en verwante fouten

Vijf filosofen delen vijf vorken die in een cirkel zijn geplaatst. Ieder heeft beide aangrenzende vorken nodig. Als ze allemaal eerst hun linkervork oppakken, kunnen ze allemaal voor altijd op hun rechtervork wachten. Als één filosoof de verwervingsvolgorde omdraait, wordt de cyclus doorbroken. Toelating beperken tot vier filosofen voorkomt ook dat alle vijf een vork vasthouden; ten minste één toegelaten filosoof kan beide vorken verkrijgen.

Uithongering betekent dat één thread onbeperkt wacht terwijl andere voortgang maken. Livelock betekent dat threads draaien en reageren, maar er herhaaldelijk niet in slagen voortgang te maken. Een verzameling threads in een deadlock is geblokkeerd. Eén thread kan zelfs zichzelf in een deadlock brengen door een gewone, niet-recursieve mutex te vergrendelen die hij al vasthoudt. Ook semaforen en conditievariabelen kunnen afhankelijkheden scheppen: een wachtende thread die een vergrendeling vasthoudt die zijn signalerende thread nodig heeft, kan een cyclus sluiten zonder een tweede expliciete verwerving van een vergrendeling.

Belangrijkste punten

  • Een deadlock is een permanente afhankelijkheidscyclus tussen geblokkeerde threads.
  • Aan alle vier de voorwaarden moet zijn voldaan: uitsluiting, vasthouden en wachten, geen gedwongen ontneming en circulair wachten.
  • Bij bronnen met één exemplaar wijst een wachtcyclus op een deadlock.
  • Een cyclus met bronnen met meerdere exemplaren vereist verdere analyse.
  • Uithongering, livelock en deadlock beschrijven verschillende problemen met voortgang.
  • Verborgen vergrendeling binnen functies kan de verwervingsvolgorde omkeren.

4.2 Sluit een voorwaarde voor deadlock uit

Preventie verandert het ontwerp zodat een noodzakelijke voorwaarde voor deadlock niet kan ontstaan. Een vaste vergrendelingsvolgorde is vaak de duidelijkste aanpak, omdat die de circulaire afhankelijkheid rechtstreeks aanpakt.

Geef elke vergrendeling een plaats in de volgorde

Ken unieke sleutels aan vergrendelingen toe en verwerf lagere sleutels altijd vóór hogere. Sorteer bij overboekingen de rekeningidentificaties voordat je vergrendelt. Zowel transfer(x, y) als transfer(y, x) verwerft dan eerst dezelfde rekening. Als de identificaties dezelfde rekening aanduiden, verwerf je de vergrendeling daarvan één keer.

Voor een cyclus zouden de sleutels rond de cirkel steeds moeten toenemen en toch naar de beginsleutel moeten terugkeren. Dat is onmogelijk. Elk verwervingspad moet de regel volgen. Eén hulpfunctie die de volgorde omkeert, brengt het risico terug. De Linux-tool lockdep houdt waargenomen vergrendelingsafhankelijkheden bij en meldt mogelijk onveilige volgordes.

Verwerf alles of niets

Een strikte manier om vasthouden en wachten uit te sluiten, is een thread zijn volledige verzameling bronnen in één stap toe te kennen, of niets toe te kennen. De thread houdt dan nooit een gedeeltelijke verzameling vast terwijl hij op de rest wacht. Hiervoor moet vooraf bekend zijn welke bronnen nodig zijn en de gelijktijdigheid kan erdoor afnemen.

Een verwant protocol laat verwervingsreeksen na elkaar verlopen met een globale preventievergrendeling:

lock(&prevention);
lock(&L1);
lock(&L2);
unlock(&prevention);
/* use L1 and L2, then release them */

Alle threads moeten hun volledige verzameling onder die globale vergrendeling verwerven, en het vrijgeven van bronnen mag niet vereisen dat ze die verwerven. Eén thread kan nog steeds L1 vasthouden terwijl hij op L2 wacht, dus dit sluit vasthouden en wachten niet letterlijk uit. Het voorkomt dat andere threads een concurrerende gedeeltelijke verwerving beginnen. Geen thread die bronnen vasthoudt, kan halverwege een tweede concurrerende verwervingsreeks zijn. De kosten zijn dat vergrendelingen eerder worden vastgehouden en dat verwervingen na elkaar verlopen.

Geef op en probeer opnieuw

pthread_mutex_trylock() keert direct terug met EBUSY als een vergrendeling niet beschikbaar is. Een thread kan L1 verwerven, L2 proberen en L1 vrijgeven als de poging mislukt. Ook andere gedeeltelijk verworven bronnen moet hij vrijgeven voordat hij het opnieuw probeert.

Twee threads kunnen herhaaldelijk botsen, opgeven en het tegelijk opnieuw proberen. Dat is livelock: beide worden uitgevoerd, maar geen van beide wordt voltooid. Een willekeurige wachttijd vóór een nieuwe poging vermindert herhaalde botsingen; die maakt van de herhalingslus geen garantie op eerlijkheid.

Vervang een vergrendeling door een atomaire bewerking

Compare-and-swap (CAS) verandert een locatie alleen als die nog overeenkomt met een verwachte waarde. Conceptueel leest een herhalingslus voor atomair optellen de oude waarde, probeert die te vervangen door de oude waarde plus de verhoging en probeert het opnieuw als een andere thread de waarde eerst heeft veranderd. Werkelijke C-implementaties gebruiken atomaire typen en atomaire bewerkingen voor zowel het lezen als het wijzigen.

Als de bewerking geen vergrendeling meer verwerft, kan ze niet meer door die vergrendeling in een deadlock raken. Een vergrendelingsvrij algoritme garandeert voortgang voor het systeem als geheel, maar één thread kan bij nieuwe pogingen steeds blijven verliezen. Algemene vergrendelingsvrije structuren vereisen ook een zorgvuldig ontwerp van de geheugenlevensduur.

TechniekBelangrijkste afweging
Verwerving in vaste volgordeElk codepad moet de volgorde volgen.
Alles tegelijk verwervenBronnen moeten bekend zijn en kunnen ongebruikt blijven.
Trylock en opnieuw proberenHerhalingswerk en livelock vragen aandacht.
Vergrendelingsvrije wijzigingCorrectheid is lastiger te beredeneren.

Belangrijkste punten

  • Een gemeenschappelijke vergrendelingsvolgorde voorkomt circulair wachten alleen als alle paden die volgen.
  • Echte alles-of-nietsverwerving sluit vasthouden en wachten uit.
  • Een globale verwervingsvergrendeling laat volledige verwervingsreeksen na elkaar verlopen.
  • Geef bij een mislukte trylock gedeeltelijk verworven bronnen vrij voordat je het opnieuw probeert.
  • Een willekeurige wachttijd vóór een nieuwe poging vermindert botsingen die tot livelock leiden.
  • Vergrendelingsvrije voortgang voor het systeem garandeert geen voortgang voor elke thread.

4.3 Toets of een bronverzoek veilig is

Vermijding controleert of een voorgesteld verzoek een mogelijkheid openlaat voor elke thread om zijn werk af te maken. De gebruikelijke patronen voor brongebruik zijn toegestaan, maar verzoeken die de resterende veilige voltooiingsvolgordes wegnemen, worden uitgesteld.

Veilig betekent dat een volgorde mogelijk is

Een veilige volgorde ordent threads zo dat elke thread zijn resterende maximale behoefte kan verkrijgen uit de bronnen die nu vrij zijn plus de bronnen die eerdere threads vrijgeven. In een onveilige toestand ontbreekt zo'n gegarandeerde volgorde. Er hoeft nog geen deadlock te zijn: threads kunnen minder dan hun maximum aanvragen of bronnen vroeg vrijgeven.

Planning kan gevaarlijke overlap ook vermijden door ervoor te zorgen dat twee conflicterende taken niet samen hun fasen van brongebruik ingaan. Beide op één CPU plaatsen is op zichzelf onvoldoende, omdat ze nog steeds kunnen worden onderbroken en vervlochten terwijl ze bronnen vasthouden. De vereiste garantie is dat de conflicterende fasen niet overlappen.

Stel de tabellen voor het bankiersalgoritme op

Houd voor elk brontype Available bij. Houd voor elke thread Max, Allocation en Need = Max - Allocation bij. Vergelijk vectoren component voor component, nooit op basis van hun opgetelde totalen.

Gebruik brontotalen (7, 5, 6):

ThreadToewijzingMaximumResterende behoefte
T0(1, 1, 0)(5, 3, 2)(4, 2, 2)
T1(2, 0, 1)(3, 2, 2)(1, 2, 1)
T2(1, 1, 2)(2, 2, 4)(1, 1, 2)
T3(0, 1, 1)(4, 2, 3)(4, 1, 2)

De toewijzingen bedragen samen (4, 3, 4), zodat Available = (3, 2, 2) overblijft.

Doorloop de veiligheidscontrole

Stel Work gelijk aan de beschikbaarheidsvector. Kies een onvoltooide thread waarvan de resterende behoefte binnen de gesimuleerde beschikbaarheid past, doe alsof die wordt voltooid en tel zijn toewijzing weer bij de gesimuleerde beschikbaarheid op.

  1. T1 past binnen (3, 2, 2). Teruggeven van (2, 0, 1) levert gesimuleerd beschikbaar (5, 2, 3) op.
  2. T0 past. Teruggeven van (1, 1, 0) levert (6, 3, 3) op.
  3. T2 past. Teruggeven van (1, 1, 2) levert (7, 4, 5) op.
  4. T3 past. Teruggeven van (0, 1, 1) levert (7, 5, 6) op.

Elke thread wordt in deze simulatie voltooid, dus de toestand is veilig. De gesimuleerde beschikbaarheid neemt toe met de toewijzing, niet met het maximum: de extra bronnen die tijdelijk voor voltooiing zijn toegekend, worden ook teruggegeven. De nettotoename is dus gelijk aan wat de thread oorspronkelijk vasthield.

Doe alsof je toekent en controleer opnieuw

Een verzoek moet zowel binnen de resterende behoefte van de thread als binnen de beschikbare hoeveelheid passen. Trek het vervolgens af van de beschikbare hoeveelheid, tel het op bij de toewijzing, trek het af van de resterende behoefte en voer de veiligheidscontrole uit op die voorlopige toestand.

Als T2 vanuit de oorspronkelijke tabel (1, 0, 1) aanvraagt, blijft beschikbaar (2, 2, 1) over. T1 kan nog steeds eerst gaan, gevolgd door T0, T2 en T3, dus dit is veilig.

Als T3 daarentegen (2, 1, 1) aanvraagt, blijft beschikbaar (1, 1, 1) over. De resterende behoefte van T3 is (2, 0, 1). Geen enkele thread past: T0 heeft 4 van A nodig, T1 heeft 2 van B nodig, T2 heeft 2 van C nodig en T3 heeft 2 van A nodig. Stel dit verzoek uit ondanks de bronnen die nu vrij zijn.

Elk later verzoek heeft dezelfde controle nodig. Vermijding vereist opgegeven maximale behoeften en een bekend bronmodel en is daardoor geschikter voor voorspelbare gespecialiseerde systemen dan voor algemene toepassingswerklasten.

Belangrijkste punten

  • De resterende behoefte is de maximale aanspraak min de huidige toewijzing.
  • Een veilige volgorde moet voor elk brontype afzonderlijk voldoen.
  • Tel na het simuleren van voltooiing de toewijzing op bij de gesimuleerde beschikbaarheid.
  • Een verzoek moet binnen de resterende behoefte en de beschikbare hoeveelheid passen en een veilige toestand opleveren.
  • Onveilig betekent dat er geen gegarandeerde voltooiingsvolgorde is; het betekent niet dat er al een deadlock is ontstaan.

4.4 Detecteer een deadlock en kies een herstelmethode

Een systeem kan een deadlock laten ontstaan, detecteren en ervan herstellen. Het accepteert daarbij dat af en toe werk verloren gaat in ruil voor minder beperkingen op gewone bronverzoeken. Detectie is alleen nuttig als er een uitvoerbare herstelactie is.

Zoek geblokkeerde cycli

Houd bij bronnen met één exemplaar een wachtafhankelijkheidsgraaf bij en zoek naar een cyclus, bijvoorbeeld met een diepte-eerstzoekopdracht. Stel dat de verbindingen T1 → T2, T2 → T3, T3 → T1 en T4 → T2 zijn. T1, T2 en T3 vormen de cyclus. T4 is daarachter geblokkeerd; de uitvoering van T4 zelf kan geen bron vrijgeven die de cyclus doorbreekt.

Bij meerdere exemplaren is een cyclus onvoldoende. Gebruik de beschikbare hoeveelheid, de toewijzing en het huidige openstaande verzoek van elke thread. Stel de gesimuleerde beschikbaarheid aanvankelijk gelijk aan de beschikbare hoeveelheid. Zoek herhaaldelijk een verzoek dat past, simuleer voltooiing en tel de toewijzing van die thread op bij de gesimuleerde beschikbaarheid. Anders dan bij vermijding gebruikt deze controle waar threads nu op wachten, niet hun mogelijke toekomstige maximale behoefte.

De standaarddetector voor bronnen zet aanvankelijk threads opzij die geen bronnen vasthouden: zij kunnen geen vastgehouden bronnen aan een broncyclus bijdragen. Van de threads die bronnen vasthouden, zitten de threads die na de controle onvoltooid blijven volgens het model in een deadlock. Andere threads kunnen nog steeds geen voortgang kunnen maken doordat ze van die verzameling afhankelijk zijn. Door de cyclus en de daarachter geblokkeerde afhankelijke threads apart te houden, kun je een geschikte taak voor herstel kiezen.

Herstel zonder gedeelde toestand te beschadigen

Een systeem kan één of meer processen beëindigen, bronnen terugwinnen door terug te draaien naar een checkpoint, of opnieuw opstarten. Kies welke taken je afbreekt op basis van herstelkosten, prioriteit, al verricht werk en vastgehouden bronnen. Controleer opnieuw nadat je één taak hebt verwijderd: meerdere onafhankelijke cycli kunnen meer dan één herstelactie vereisen.

Een willekeurige thread beëindigen is niet genoeg als daardoor een mutex vergrendeld blijft of een gedeelde structuur half gewijzigd achterblijft. Herstel moet bronnen vrijgeven of reconstrueren en een geldige toestand herstellen. Transactiesystemen kunnen een transactie in een deadlock afbreken omdat terugdraaien deel uitmaakt van hun ontwerp. Een gewone toepassing heeft vaak een gecontroleerde herstart van het proces nodig.

Interpreteer time-outs zorgvuldig

pthread_mutex_timedlock() kan na een uiterste tijd ETIMEDOUT teruggeven. Een waakhond kan een apparaat resetten dat geen voortgang meer meldt. Beide detecteren buitensporig lang wachten; lang wachten kan ook door trage I/O of planning komen. Een time-out is een aanwijzing om onderzoek te doen of te herstellen, geen eenduidige diagnose van een cyclus.

StrategieWat is nodigBelangrijkste kosten
PreventieEen ontwerpregel die een noodzakelijke voorwaarde uitsluit.Minder flexibiliteit of gelijktijdigheid.
VermijdingToekomstige maximale behoeften en een veiligheidscontrole.Uitgestelde verzoeken en administratie.
Detectie en herstelEen detector en een geldig herstelpad.Controlekosten en mogelijk verloren werk.
Zeldzame deadlocks negerenAanvaardbare kosten van uitval en herstart.Incidentele verstoring.

Linux en Windows vinden en herstellen doorgaans geen cycli tussen de eigen vergrendelingen van een toepassing. Het programma moet de gekozen strategie implementeren. Als herstel herhaaldelijk hetzelfde werk kiest om af te breken, kan dat werk worden uitgehongerd, ook al wordt elke afzonderlijke deadlock doorbroken.

Belangrijkste punten

  • Detectie vindt een bestaande deadlock; preventie voorkomt dat die ontstaat.
  • Detectie bij meerdere exemplaren gebruikt huidige verzoeken, niet de maximale resterende behoefte.
  • Onderscheid een broncyclus van threads die daarachter geblokkeerd zijn.
  • Herstel moet geldige bronnen en een geldige gedeelde toestand herstellen, niet alleen een thread stoppen.
  • Een time-out kan ook op een trage bewerking wijzen.
5

Houd een gebeurtenislus responsief

Gelijktijdigheid kan ontstaan doordat één thread veel taken bedient terwijl hun I/O-wachttijden overlappen. Een gebeurtenislus kiest een gereedstaande taak, voert een korte afhandelaar uit en wacht daarna weer op de volgende gebeurtenis. Hierdoor zijn geen vergrendelingen nodig tussen synchrone afhandelaars in die lus, maar zorgt elke blokkerende bewerking voor vertraging voor iedereen. Gereedheidscontroles, asynchrone I/O en opgeslagen voortzettingen houden de lus in beweging; meerdere processorkernen en gedeelde toestand vragen nog steeds aandacht.

5.1 Handel gereedstaande descriptoren af zonder te blokkeren

Een gebeurtenislus wacht op gebeurtenissen en laat korte afhandelaars op één thread uitvoeren. Het programma kiest expliciet welke gereedstaande gebeurtenis het als volgende afhandelt. Node.js en nginx gebruiken gebeurtenisgestuurde ontwerpen om veel verbindingen te bedienen zonder aan elke verbinding een eigen thread toe te wijzen.

while (1) {
    events = get_events();
    /* handle each ready event, then return to the loop */
}

Synchrone afhandelaars op dezelfde lus worden één voor één uitgevoerd. De gewone instructies van een afhandelaar worden niet door een andere afhandelaar op die lus onderbroken. De lus blijft ervan afhankelijk dat afhandelaars snel terugkeren.

Bouw select-verzamelingen op en controleer ze

select() accepteert verzamelingen descriptoren om te volgen voor lezen, schrijven en uitzonderlijke condities, plus een time-out. Het argument nfds is één groter dan de hoogste gevolgde descriptor. Voor het volgen van descriptoren 3, 5 en 9 is daarom nfds = 10 nodig, niet 3.

FD_ZERO(&readset);
FD_SET(3, &readset);
FD_SET(5, &readset);
FD_SET(9, &readset);
int n = select(10, &readset, NULL, NULL, NULL);
/* If n > 0, test each watched descriptor with FD_ISSET. */

Een null-time-out wacht op gereedheid; een time-out van nul controleert en keert direct terug. select() overschrijft de verzamelingen met de gereedstaande descriptoren, dus bouw ze vóór elke aanroep opnieuw op. Een positieve retourwaarde telt de ingestelde bits in de teruggegeven verzamelingen; een descriptor die in twee verzamelingen gereed is, telt twee keer. Nul betekent dat de time-out is verstreken; −1 betekent een fout.

Gereedheid is geen gegevensoverdracht. Het programma roept nog steeds read() of recv() aan. Het einde van de invoer kan als leesbaarheid verschijnen; een leesbewerking die 0 teruggeeft, betekent dat de andere partij haar zendzijde heeft gesloten. Handel dat resultaat af in plaats van dezelfde beëindigde invoer telkens opnieuw te laten afhandelen.

Houd afhandelaars kort en niet-blokkerend

Stel sockets in op O_NONBLOCK. Wanneer een bewerking niet verder kan, geeft die −1 terug met errno ingesteld op EAGAIN of EWOULDBLOCK, zodat de afhandelaar naar de lus kan terugkeren. De gereedheid kan vóór de bewerking veranderen, dus de niet-blokkerende modus beschermt ook tegen onverwacht wachten na een gereedheidsmelding.

Een blokkerende leesbewerking binnen een afhandelaar vertraagt elke gebeurtenis in de wachtrij. Bij 100 verzoeken in de wachtrij en een blokkerende schijfleesbewerking van 10 ms per afhandelaar wacht het laatste verzoek ongeveer 990 ms voordat zijn eigen leesbewerking begint; de reeks duurt ongeveer 1.000 ms. De thread besteedde het grootste deel van die tijd aan wachten terwijl gereed werk in de wachtrij stond.

Kies een gereedheidsinterface
InterfaceWeergave van de gevolgde verzamelingBelangrijke eigenschap
select()Descriptorbitverzamelingen die voor elke aanroep opnieuw worden opgebouwd.Descriptorwaarden moeten binnen FD_SETSIZE passen, doorgaans 1024.
poll()Een array van descriptoren en aangevraagde en teruggegeven gebeurtenissen.Geen overeenkomstige vaste grens voor descriptorwaarden.
epoll / kqueueEen geregistreerde verzameling te volgen descriptoren die de kernel bijhoudt.Efficiënte aflevering van gebeurtenissen bij grote gevolgde verzamelingen.

select() en poll() onderzoeken bij elke aanroep de opgegeven gevolgde verzameling, dus hun werk groeit met de grootte daarvan. Linux biedt epoll; FreeBSD en macOS bieden kqueue. Slechts enkele descriptoren volgen maakt een hoge descriptorwaarde niet veilig voor een fd_set met vaste grootte.

Belangrijkste punten

  • Eén lus laat synchrone afhandelaars één voor één uitvoeren.
  • Bij select is nfds de hoogste gevolgde descriptor plus één.
  • Bouw select-verzamelingen na elke aanroep opnieuw op.
  • Na een gereedheidsmelding is een I/O-aanroep nodig; een leesbewerking die 0 teruggeeft, betekent einde van de invoer.
  • Niet-blokkerende sockets laten afhandelaars terugkeren wanneer een bewerking anders zou wachten.
  • FD_SETSIZE beperkt descriptorwaarden, niet alleen het aantal gevolgde descriptoren.

5.2 Bewaar het werk dat na I/O doorgaat

Netwerkgereedheid vertelt een lus wanneer die I/O kan proberen. Gewone schijfbestanden zijn anders: select() en poll() melden ze als gereed, ook als toegang tot hun gegevens nog op de opslag wacht. Dien werk asynchroon in en handel het resultaat later af om andere gebeurtenissen te kunnen blijven bedienen.

Dien in, werk elders verder en haal het resultaat op

POSIX slaat een verzoek voor asynchrone I/O op in struct aiocb. Belangrijke velden zijn aio_fildes voor de bestandsdescriptor, aio_offset voor de bestandspositie, aio_buf voor de bestemming en aio_nbytes voor de aangevraagde lengte.

/* cb describes a read request */
aio_read(&cb);
/* Return to other work. Later: */
int status = aio_error(&cb);
/* EINPROGRESS: still running; 0: succeeded; otherwise an error. */
/* After completion, aio_return(&cb) collects its result. */

aio_read() plaatst het verzoek in de wachtrij en keert terug. Voltooiing is een afzonderlijke gebeurtenis. Wanneer aio_error() voltooiing aangeeft, levert aio_return() het resultaat, zoals het aantal gelezen bytes na een succesvolle bewerking. Een verzoek kan mislukken en een succesvolle leesbewerking kan minder bytes teruggeven dan aangevraagd. Het verzoekrecord en de buffer moeten geldig blijven zolang de bewerking openstaat.

Elk verzoek steeds opnieuw in een korte lus doorzoeken verspilt CPU-tijd. aio_sigevent kan een voltooiingssignaal of callback instellen. Linux io_uring gebruikt indienings- en voltooiingswachtrijen; Windows-I/O-voltooiingspoorten bieden afhandeling op basis van voltooiing. Met deze interfaces kan het programma op voltooid werk reageren zonder voortdurend elk onvoltooid verzoek opnieuw te proberen.

Bewaar de toestand gedurende de tussenliggende tijd

Een geblokkeerde thread bewaart de lokale variabelen van zijn functie op zijn stack. Een gebeurtenisafhandelaar die I/O indient en terugkeert, moet opslaan wat de latere afhandelaar nodig heeft. Deze expliciete administratie is handmatig stackbeheer; de opgeslagen toestand is een voortzetting.

Stel dat een server bestandsdescriptor 12 leest voor een client op socket 7. Bij indiening slaat de server een record op dat het leesverzoek aan socket 7 en de toestand van het antwoord koppelt. Wanneer de voltooiing binnenkomt, vindt de lus het record, controleert het leesresultaat en gaat verder met het sturen van het antwoord naar de juiste client. Afzonderlijke records voorkomen dat de bestemmingen van meerdere openstaande verzoeken door elkaar raken.

De voortzetting moet aan de specifieke bewerking zijn gekoppeld, niet alleen aan een identificatie die later opnieuw kan worden gebruikt. Houd de clientverwijzing geldig totdat de voltooiing is afgehandeld en verwijder het record wanneer de taak klaar is. Deze details over de levensduur vervangen aannames die een rechtlijnige blokkerende functie op zijn stack kan bewaren.

Leesbare syntaxis kan een opschorting verbergen

Callbacks, beloften en async/await automatiseren een groot deel van dit opslaan van toestand. In JavaScript schort const data = await readFile(path); die functie op zolang de I/O openstaat. Ander werk in de lus kan worden uitgevoerd en de functie gaat later verder met haar lokale toestand hersteld.

Asynchroon betekent dat de aanroeper kan doorgaan voordat de bewerking is voltooid. Het betekent niet dat de afhandelaars ervan tegelijk op meerdere processorkernen draaien. Ook geeft CPU-intensieve code na een await daardoor niet automatisch de beurt af: die code bezet de uitvoerende thread totdat ze terugkeert of opnieuw wordt opgeschort.

Belangrijkste punten

  • Gereedheid van een gewoon bestand garandeert niet dat een schijfleesbewerking zonder wachten verloopt.
  • Asynchrone I/O scheidt het indienen van voltooiing en het ophalen van het resultaat.
  • Een voortzetting bewaart de toestand die nodig is nadat de oorspronkelijke afhandelaar terugkeert.
  • Houd verzoekrecords en buffers geldig tot de voltooiing.
  • Async/await bewaart toestand tijdens opschorting; het creëert geen uitvoering op meerdere processorkernen.

5.3 Kies gebeurtenissen, threads of een combinatie

Een gebeurtenislus past goed bij veel verbindingen die meestal niets doen en af en toe kort werk opleveren. Een threadgebaseerd ontwerp houdt de besturingsstroom en stack van een taak bij elkaar. De beste keuze hangt af van wachttijd, CPU-werk, geheugen en hoe de code zich zal ontwikkelen.

Vergelijk wat elk ontwerp met zich meebrengt
AandachtspuntGebeurtenisgestuurd ontwerpThreadgebaseerd ontwerp
Verbindingen die meestal niets doenKlein toestandsrecord per verbinding.Een thread brengt stack- en planningsoverhead mee.
Blokkerend werkBesteed het uit of splits rond de voltooiing.De aanroepende thread kan wachten terwijl andere draaien.
Meerdere processorkernenVoer meerdere lussen uit of gebruik werkthreads.Plan uitvoerbare threads over de kernen.
Gedeelde veranderlijke gegevensEén synchrone lus voorkomt overlap tussen afhandelaars.Gedeelde toegangen vereisen synchronisatie.
Een functie gaat blokkerenDe aanroepers moeten mogelijk rond een voortzetting worden geherstructureerd.De rechtlijnige besturingsstroom kan behouden blijven.

Een chatserver met 50.000 verbindingen die meestal niets doen, kan baat hebben bij gebeurtenisrecords in plaats van een eigen threadstack per verbinding. Een afbeeldingsverwerker die veel rekenwerk per verzoek doet, heeft genoeg werkthreads nodig om de beschikbare kernen te gebruiken; één bezige lus kan die berekeningen niet parallel maken.

Verborgen wachttijden blijven van belang

Een afhandelaar kan vastlopen zonder een duidelijke blokkerende API-aanroep. Toegang tot een pagina die naar schijf is gewisseld, kan een schijfleesbewerking veroorzaken. Tijdens die paginafout wacht de thread van de lus. mlock() kan benodigde pagina's in het geheugen houden als het systeem de gevraagde hoeveelheid vastgezet geheugen toestaat. Alleen expliciete blokkerende aanroepen verwijderen neemt niet elke oorzaak van vertraging weg.

Codewijzigingen kunnen een ander verborgen wachtmoment introduceren. Als een hulpfunctie die voorheen direct terugkeerde gegevens gaat ophalen, moet een gebeurtenisgestuurde aanroeper zijn werk rond een voortzetting splitsen of de aanroep uitbesteden. De stack van een blokkerende thread bewaart al waar de uitvoering moet worden hervat en welke lokale toestand daarvoor nodig is.

Combineer de sterke punten

Een hybride ontwerp houdt de I/O-gereedheid van het netwerk bij in de gebeurtenislus en stuurt blokkerende of CPU-intensieve taken naar een pool van werkthreads. Voltooiingen keren terug naar de lus voor het afhandelen van het antwoord. Node.js gebruikt de threadpool van libuv voor bestandssysteembewerkingen, zodat een asynchrone interface door achtergrondthreads kan worden geïmplementeerd.

Om meerdere kernen te gebruiken, kan een server één gebeurtenislus per kern uitvoeren in afzonderlijke threads of processen. Als die lussen veranderlijke gegevens delen, hebben ze de gebruikelijke vergrendelingen of een ander synchronisatieprotocol nodig. Het voordeel dat geen vergrendeling nodig is, geldt voor synchrone afhandelaars op één lus, niet voor elk deel van een gebeurtenisgestuurde server.

Een taak kan over await heen worden vervlochten

Stel dat er nog één exemplaar op voorraad is. Een afhandelaar controleert de beschikbaarheid, wacht op de betaling en vermindert daarna de voorraad. Terwijl die is opgeschort, kan een andere afhandelaar dezelfde voorraad controleren en ook een betaling starten. Beide controles kunnen slagen voordat een van beide de voorraad vermindert. Er worden geen twee synchrone afhandelaars tegelijk uitgevoerd, maar de volledige controle en wijziging vormen toch geen atomaire bewerking.

Reserveer de voorraad als één gesynchroniseerde toestandsverandering voordat de thread de beurt afgeeft, met een manier om de reservering vrij te geven als de betaling mislukt, of controleer opnieuw onder een protocol dat een tweede verkoop voorkomt. Een mutex vasthouden tijdens lang wachten kan andere voortgangsproblemen veroorzaken, dus ontwerp de toestandsovergang en het herstel daarvan expliciet. Ook bij gebeurtenisgestuurde code moet je beredeneren welke feiten tijdens elke opschorting waar blijven.

Belangrijkste punten

  • Gebeurtenissen passen bij veel inactieve verbindingen; CPU-intensief werk heeft parallelle werkthreads nodig.
  • Eén gebeurtenislus gebruikt één kern; gedeelde gegevens tussen lussen vereisen synchronisatie.
  • Paginafouten en hulpfuncties die gaan blokkeren, kunnen een lus laten vastlopen.
  • Hybride servers besteden blokkerend werk uit en laten voltooiingen naar de lus terugkeren.
  • Een taak die een await omvat, kan een schending van atomiciteit tussen controleren en handelen vertonen.

Deadlocks en semaforen: voorbeelden

5 Walkthroughs

1

Van een voltooiingsvlag naar een werkende wachtrij

12 stappen
Volg slapen, wekken, opnieuw controleren en het doorgeven van meldingen tussen producenten en consumenten.
  1. 1
    IntroductieWacht zonder CPU-tijd te verspillen

    Een ouder heeft het resultaat van zijn kind nodig, maar het kind kan klaar zijn vóór of nadat de ouder begint te wachten. Een werkend protocol moet beide volgordes aankunnen.

    Gebruik een gedeelde vlag done om voltooiing te onthouden, een mutex om die te beschermen en een conditievariabele om de ouder te laten slapen. Volg hoe de mutex wordt overgedragen terwijl elke thread wordt uitgevoerd.

  2. 2
    VoorbeeldDe ouder arriveert eerst

    Aanvankelijk is done = 0. De ouder vergrendelt m, controleert de vlag en roept wait(&c, &m) aan binnen een while-lus.

    De wachtoproep geeft de mutex atomair vrij en begint te wachten. Het kind kan nu m verwerven. Er is geen ontgrendelde tussenruimte tussen het controleren van de conditie en het beginnen met wachten waarin het kind een melding kan sturen die de ouder mist.

  3. 3
    VoorbeeldHet kind legt voltooiing vast

    Het kind verwerft m, slaat done = 1 op, signaleert c en ontgrendelt. Het signaal maakt de ouder gereed; het laat de ouder niet onmiddellijk uitvoeren.

    De ouder verwerft m opnieuw voordat de wachtoproep terugkeert, controleert done opnieuw en gaat verder omdat de voltooide toestand van het kind nu is vastgelegd. Beide threads gebruiken dezelfde mutex voor de gedeelde vlag.

  4. 4
    UitdagingDraai hun aankomstvolgorde om

    Het kind is klaar en signaleert voordat de ouder zijn wachtoproep heeft bereikt. Wat voorkomt dat de ouder voor altijd slaapt?

    ---reveal---

    Het kind heeft onder de mutex al done = 1 ingesteld. De ouder controleert die vlag terwijl hij de mutex vasthoudt en slaat het wachten over. Het vroege signaal wordt niet bewaard; de toestandsvlag bewaart de informatie bij de andere aankomstvolgorde.

  5. 5
    VoorbeeldEen weksignaal reserveert geen element

    C1 wacht op een buffer met één plaats. Een producent voegt een element in en signaleert C1. Voordat C1 de mutex opnieuw verwerft, verwerft C2 die en verwijdert het element.

    Bij Mesa-semantiek is C1 alleen gereed om mee te dingen. C1 moet de buffer opnieuw controleren nadat hij de mutex heeft verworven. Terugkeren uit de wachtoproep vervangt niet de controle of het element er nog is.

  6. 6
    UitdagingKies de juiste hercontrole

    In dat verloop gebruikte C1 if (count == 0) wait(...). C1 keert nu terug nadat C2 de plaats heeft geleegd. Welke codewijziging voorkomt een ongeldige verwijdering?

    ---reveal---

    Gebruik while (count == 0) wait(...). Nadat C1 de mutex opnieuw heeft verworven, ziet hij de lege buffer en wacht opnieuw. Dezelfde lus vangt ook spontaan ontwaken op en is dus ook zonder tweede consument nodig.

  7. 7
    VoorbeeldWek elke thread die verder kan

    Een allocator heeft 0 vrije bytes. Ta wacht op 100 bytes en Tb op 10. Door 50 vrij te geven kan alleen Tb verder, maar een signaal kan Ta wekken.

    Een broadcast bereikt beide. Elk verwerft de mutex opnieuw en controleert de eigen aangevraagde hoeveelheid. Tb kan 10 nemen; Ta wacht opnieuw. De predicaatlus zorgt voor correctheid, terwijl een brede melding voorkomt dat een thread die verder kan blijft slapen.

  8. 8
    VoorbeeldBehoud de wachtrijvolgorde bij terugspringen

    De capaciteit is 3. Voeg 7, 8, 9 in; verwijder 7; voeg 10 in. De fysieke array is nu [10, 8, 9].

    De leesindex wijst naar plaats 1, dus de verwijdervolgorde is 8, 9, 10. Verhoog beide indices modulo de capaciteit en verander count onder de mutex. De weergave van de array van links naar rechts is niet noodzakelijk de logische volgorde van de wachtrij.

  9. 9
    KernpuntGeef elke rol een eigen wachtrij

    Een producent wacht op empty zolang de buffer vol is, voegt in en signaleert daarna fill. Een consument wacht op fill zolang de buffer leeg is, verwijdert en signaleert daarna empty. Beide conditievariabelen gebruiken de buffermutex.

    De afzonderlijke wachtrijen zijn van belang: een melding over een element bereikt een consument en een melding over ruimte bereikt een producent. Eén gedeelde signaalwachtrij zou een thread kunnen kiezen waarvan de conditie nog onwaar is.

  10. 10
    UitdagingZoek de slapende producent

    Een buffer met één plaats gebruikt één gedeelde conditiewachtrij en while-lussen. P slaapt omdat de buffer vol is. C1 verwijdert het element, signaleert C2 en wacht daarna omdat de buffer leeg is. C2 ziet ook dat de buffer leeg is en wacht. Waarom blijft de ruimte ongebruikt?

    ---reveal---

    P heeft de melding over ruimte nooit ontvangen. Al het uitvoerbare werk is gestopt, hoewel een producent verder zou kunnen. Afzonderlijke wachtrijen voor producenten en consumenten leiden het signaal van C1 naar P.

  11. 11
    UitdagingBeëindig het werk zonder elementen te verliezen

    Twee producenten genereren nog verzoeken. Een coördinator wil nu voor elke consument één stopmarkering toevoegen. Wat moet de coördinator veranderen en waarom?

    ---reveal---

    Wacht eerst tot alle producenten klaar zijn. Plaats pas daarna de stopmarkeringen in de wachtrij. Anders kunnen consumenten de markeringen pakken en stoppen terwijl producenten nog echte verzoeken moeten invoegen. Voor het invoegen en verwijderen van markeringen wordt nog steeds de normale wachtrijsynchronisatie gebruikt.

  12. 12
    SamenvattingControleer een conditiewachtoproep

    Bepaal voor elke wachtoproep de toestand, mutex en wachtrij. Houd de predicaatcontrole onder de mutex, begin te wachten zonder ontgrendelde tussenruimte en controleer na elke terugkeer opnieuw.

    Bekijk daarna het signaleringspad: heeft het de toestand veranderd en kan de melding een thread bereiken die nu verder kan? Die vragen brengen zowel verloren weksignalen als vastlopende buffers door het wekken van de verkeerde rol aan het licht.

2

Volg toegangsbewijzen door echte protocollen

15 stappen
Kies aantallen, volg overdrachten en houd reserveringen van bronnen gescheiden van wederzijdse uitsluiting.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBenoem wat één toegangsbewijs betekent
  2. 2
    VoorbeeldVolg een binaire semafoor
  3. 3
    UitdagingHerstel een verkeerd beginaantal
  4. 4
    VoorbeeldOnthoud een vroege voltooiing
  5. 5
    VoorbeeldBegrens een geheugenintensieve fase
  6. 6
    UitdagingScheid toelating van administratie
  7. 7
    VerbandOntmoet elkaar voordat je verdergaat
  8. 8
    UitlegReserveer, wijzig en maak beschikbaar
  9. 9
    VoorbeeldVolg een geblokkeerde consument
  10. 10
    UitdagingHerken de vergrendelingsafhankelijkheid
  11. 11
    UitlegLaat een groep lezers één toegangsbewijs vasthouden
  12. 12
    VoorbeeldBekijk de wachtende schrijver
  13. 13
    UitdagingHandel een benodigde wijziging af
  14. 14
    UitdagingBehoud beide soorten bescherming
  15. 15
    SamenvattingControleer elk verworven toegangsbewijs
3

Diagnosticeer de verborgen vervlechting

9 stappen
Onderscheid een bewerking die bijeen moet blijven van een bewerking die eerst moet plaatsvinden.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVraag wat de code aanneemt
  2. 2
    VoorbeeldPlaats een verandering in de tussenruimte
  3. 3
    VoorbeeldMaak de beslissing over het saldo ondeelbaar
  4. 4
    UitdagingZoek de ontbrekende grens van de kritieke sectie
  5. 5
    VoorbeeldEen nieuwe werkthread kan eerst draaien
  6. 6
    UitlegMaak de voltooide toestand bekend
  7. 7
    UitdagingVervang een gok over timing
  8. 8
    UitdagingKies tussen de twee oplossingen
  9. 9
    SamenvattingTest de garantie, niet de vertraging
4

Van een vergrendelingscyclus naar een veilig verzoek

17 stappen
Volg een deadlock, sluit een voorwaarde uit, toets de veiligheid van bronnen en plan herstel.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieOnderscheid drie strategieën
  2. 2
    VoorbeeldSluit een cyclus tussen twee threads
  3. 3
    UitlegBenoem de noodzakelijke voorwaarden
  4. 4
    UitdagingOnderscheid een keten van een cyclus
  5. 5
    VoorbeeldOrden beide richtingen op dezelfde manier
  6. 6
    TipGeef gedeeltelijk verworven bronnen vrij vóór een nieuwe poging
  7. 7
    UitdagingControleer de verborgen hulpfunctie
  8. 8
    VoorbeeldBereken de resterende aanspraken
  9. 9
    VoorbeeldZoek een thread die eerst kan worden voltooid
  10. 10
    VoorbeeldVoltooi de veiligheidsvolgorde
  11. 11
    KernpuntEen toekenning uit vrije bronnen kan toch onveilig zijn
  12. 12
    UitdagingVergelijk bronnen per type
  13. 13
    VerbandDetecteer de huidige wachttoestand
  14. 14
    UitlegHerstel naar een geldige toestand
  15. 15
    UitdagingKies een geschikt doel voor herstel
  16. 16
    UitdagingInterpreteer een onveilig resultaat
  17. 17
    SamenvattingStem de controle af op de vraag
5

Handel gebeurtenissen af terwijl I/O loopt

13 stappen
Bouw een gereedheidslus, sla voortzettingen op en vind de resterende gelijktijdigheidsrisico's.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVerplaats het wachten naar de grens van de lus
  2. 2
    VoorbeeldBouw de gevolgde verzameling van select op
  3. 3
    UitlegVoer de daadwerkelijke leesbewerking uit
  4. 4
    UitdagingControleer een hoge descriptorwaarde
  5. 5
    UitlegVerwar gereedheid van een bestand niet met voltooiing
  6. 6
    VoorbeeldSla de bestemming van het antwoord op
  7. 7
    UitdagingInterpreteer een lopende bewerking
  8. 8
    VerbandLaat await de lokale toestand bewaren
  9. 9
    VoorbeeldGebruik werkthreads voor kostbaar werk
  10. 10
    TipLet op impliciet wachten
  11. 11
    UitdagingZoek de tussenruimte rond await
  12. 12
    UitdagingHoud de grens tussen processorkernen zichtbaar
  13. 13
    SamenvattingControleer elk opschortingspunt

Deadlocks en semaforen: oefenvragen

50 Oefenvragen in 5 sets

Set 1: Wachten, toegangsbewijzen en afhankelijkheden10 Oefenvragen

Vraag 1 van 10

Een dienst heeft zeven identieke, ongebruikte apparaathandles. Met welke beginwaarde van de semafoor kunnen alle beschikbare handles worden geleend?
  1. 0
  2. 1
  3. 7
  4. 8
Toon antwoordVerberg antwoord
Antwoord: C
Initialiseer de semafoor op 7, omdat zeven eenheden direct beschikbaar zijn. Elke lener gebruikt één toegangsbewijs en geeft het na gebruik terug. Beginnen bij 1 zou alle leners na elkaar laten werken, hoewel de verzameling er zeven kan bedienen; beginnen bij 0 zou de eerste lener blokkeren.

Vraag 2 van 10

Welke lus moet een POSIX-wachtoproep op een conditievariabele omgeven wanneer een consument een niet-lege wachtrij nodig heeft?
  1. while (count == 0)
  2. if (count == 0)
  3. while (count > 0)
  4. if (count > 0)
Toon antwoordVerberg antwoord
Antwoord: A
Wacht zolang de wachtrij leeg is en controleer na elke terugkeer opnieuw. De wachtrij kan zijn veranderd voordat de ontwaakte thread de mutex opnieuw verwerft en spontaan ontwaken is toegestaan. Een if voert geen tweede controle uit en kan de consument daardoor uit een lege wachtrij laten verwijderen.

Vraag 3 van 10

Welk beginaantal van een semafoor laat precies één thread een kritieke sectie binnengaan vóór een vrijgavebewerking?
  1. 0
  2. 2
  3. −1
  4. 1
Toon antwoordVerberg antwoord
Antwoord: D
Eén aanvankelijk toegangsbewijs laat de eerste thread passeren en laat een tweede wachten op een teruggegeven toegangsbewijs. Een aantal van 0 blokkeert daarentegen de eerste thread, zodat die de kritieke sectie en zijn latere vrijgavebewerking niet kan bereiken. Een negatieve beginwaarde is geen geldige POSIX-semafoorinitialisatie.

Vraag 4 van 10

Een machine heeft 12 GB RAM. Ander werk heeft 3 GB nodig en elke toegelaten werkthread heeft 750 MB nodig. Hoeveel werkthreads passen met decimale SI-eenheden in het resterende geheugen?
  1. 16
  2. 12
  3. 9
  4. 15
Toon antwoordVerberg antwoord
Antwoord: B
Na reservering van 3 GB is voor de werkthreads 9 GB, of 9.000 MB, beschikbaar. Deel 9.000 MB door 750 MB en je krijgt 12 gelijktijdige werkthreads. Bij toelating van 16 zouden de werkthreads de volledige 12 GB gebruiken en zou geen geheugen overblijven voor het opgegeven andere werk.

Vraag 5 van 10

Twee threads reserveren elk vier plaatsen. Er zijn nog zes plaatsen en beide beschikbaarheidscontroles zijn klaar voordat een van beide reserveert. Welke afbakening van de kritieke sectie voorkomt overboeking?
  1. Eén vergrendeld gebied voor controle, daarna één voor reservering.
  2. Eén vergrendeld gebied rond controle en reservering.
  3. Een leesvergrendeling rond de volledige reservering van elke thread.
  4. Een afzonderlijke eigen mutex rond elke reservering.
Toon antwoordVerberg antwoord
Antwoord: B
De controle van de plaatsen moet geldig blijven tot de bijbehorende reservering, dus bescherm beide binnen één kritieke sectie die alle reserverende threads delen. Afzonderlijke vergrendelde toegangen laten nog steeds toe dat beide controles zes beschikbare plaatsen zien voordat een van beide er vier reserveert. Een eigen mutex kan de concurrerende thread niet uitsluiten.

Vraag 6 van 10

Een rapportagethread heeft een eindtotaal nodig dat een werkthread vlak voor zijn beëindiging schrijft. Welke bewerking zorgt voor voltooiing voordat het rapport het totaal leest?
  1. Slaap kort voordat je leest.
  2. Sta de CPU-beurt af voordat je leest.
  3. Vergrendel alleen rond het lezen.
  4. Wacht op threadbeëindiging voordat je leest.
Toon antwoordVerberg antwoord
Antwoord: D
Wachten op de beëindiging van de werkthread duurt tot die is beëindigd, zodat het voltooide totaal beschikbaar is voordat het rapport het leest. Slapen is slechts een gok over hoe lang het werk duurt. Alleen het lezen vergrendelen kan niet vereisen dat de werkthread eerst klaar is of het resultaat heeft geschreven.

Vraag 7 van 10

Thread H houdt M vast en wacht op N. Thread J houdt N vast en wacht op M. Beide vergrendelingen zijn exclusief en kunnen niet gedwongen worden afgenomen. Wat beschrijft deze twee threads?
  1. Een verzameling in deadlock.
  2. Een uitgehongerde groep.
  3. Een paar in livelock.
  4. Een uitvoerbaar paar.
Toon antwoordVerberg antwoord
Antwoord: A
Elke thread wacht op een vergrendeling die alleen de geblokkeerde andere thread kan vrijgeven, waardoor een permanente cyclus ontstaat. Dit is een deadlock, waarbij beide threads geblokkeerd zijn. Bij livelock zouden ze moeten blijven draaien en reageren, terwijl bij uithongering ander werk voortgang maakt terwijl een wachtende thread wordt uitgesteld.

Vraag 8 van 10

Een overboeking vergrendelt rekeningobjecten met unieke sleutels. Welke verwervingsregel voorkomt dat overboekingen in tegengestelde richtingen circulair wachten veroorzaken?
  1. Verwerf de bronrekening vóór de doelrekening.
  2. Verwerf de doelrekening vóór de bronrekening.
  3. Verwerf beide rekeningen in oplopende sleutelvolgorde.
  4. Verwerf eerst de rekening met het kleinste saldo.
Toon antwoordVerberg antwoord
Antwoord: C
Een gemeenschappelijke oplopende sleutelvolgorde laat beide overboekingsrichtingen dezelfde eerste vergrendeling aanvragen en voorkomt zo het tegengestelde verwervingspatroon. Eerst de bron vergrendelen keert de volgorde om wanneer de overboekingsrichting omkeert. Een volgorde op basis van saldo kan veranderen als saldi veranderen en is dus niet de vaste gemeenschappelijke sleutelregel.

Vraag 9 van 10

Een lus volgt descriptoren 2, 8 en 14 met select. Welke waarde moet de lus voor nfds doorgeven?
  1. 3
  2. 14
  3. 15
  4. 16
Toon antwoordVerberg antwoord
Antwoord: C
Select onderzoekt descriptornummers van 0 tot en met nfds - 1, dus de hoogste gevolgde descriptor, 14, vereist 15. Het argument is gebaseerd op de grootste descriptorwaarde, niet op de drie vermeldingen in de gevolgde verzameling. Als je 14 doorgeeft, valt descriptor 14 buiten het onderzochte bereik.

Vraag 10 van 10

Afhandelaar A dient een leesverzoek in voor clientsocket 40. Afhandelaar B dient daarna een ander leesverzoek in voor socket 41. Beide voltooiingsafhandelaars gebruiken een globale bestemming, nu 41. De leesbewerking van A wordt eerst voltooid. Waarheen gaat het antwoord van A en welke oplossing houdt beide clients correct?
  1. Naar 40; blijf de globale bestemming gebruiken.
  2. Naar 41; sla de bestemming per verzoek op.
  3. Naar 40; bouw vóór het antwoorden de select-verzameling opnieuw op.
  4. Naar 41; houd alleen een teller van openstaande verzoeken bij.
Toon antwoordVerberg antwoord
Antwoord: B
De voltooiing van A leest de overschreven globale waarde 41 en stuurt het antwoord dus naar de client van B in plaats van naar socket 40. Sla elke bestemming bij het eigen verzoek op en zoek die bij voltooiing op. Snellere voltooiingsafhandeling kan niet maken dat één globale bestemming twee openstaande clients bewaart.
Set 2: Volg het protocol door een uitvoeringsvolgorde10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een ringbuffer heeft capaciteit 4 en begint leeg met beide indices op 0. De buffer ontvangt 21, 22, 23, verwijdert twee elementen en ontvangt daarna 24 en 25. Wat is nu de logische verwijdervolgorde?
  1. 25, 23, 24
  2. 23, 24, 25
  3. 24, 25, 23
  4. 21, 24, 25
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een laadthread stelt ready onder een mutex in en signaleert voordat een gebruiker begint te wachten. De gebruiker vergrendelt later die mutex en controleert ready voordat die beslist of wachten nodig is. Waarom kan de gebruiker verder?
  1. De conditievariabele heeft het signaal opgeslagen.
  2. De mutex vergrendelen speelt de melding opnieuw af.
  3. Het signaal droeg de mutex over aan de latere gebruiker.
  4. De gereedheidstoestand blijft na het signaal bestaan.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een buffer met semaforen heeft capaciteit 6 en bevat aanvankelijk twee elementen. Er zijn geen bewerkingen gaande. Wat zijn de aantallen toegangsbewijzen voor empty en full?
  1. Empty 4; full 2.
  2. Empty 6; full 2.
  3. Empty 2; full 4.
  4. Empty 4; full 0.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Drie lezers houden de basisvergrendeling met lezersvoorrang vast en een schrijver wacht. Twee lezers vertrekken. Wat verhindert de schrijver om nu naar binnen te gaan?
  1. De schrijver moet drie afzonderlijke schrijftoegangsbewijzen verzamelen.
  2. De laatst vertrekkende lezer heeft writelock al vrijgegeven.
  3. De overgebleven lezer houdt de toegang van de groep nog vast.
  4. De wachtende schrijver blijft eigenaar van de tellervergrendeling.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een gedeeld saldo gebruikt atomaire laad- en aftrekbewerkingen. Twee threads controleren elk of er ten minste 45 eenheden zijn en trekken daarna 45 af; het saldo begint op 70. Welke fout blijft mogelijk als beide controles eerst worden voltooid?
  1. De tweede aftrekking weigert de afschrijving.
  2. De atomaire laadbewerkingen reserveren elk 45 eenheden.
  3. Beide afschrijvingen kunnen doorgaan en −20 overlaten.
  4. Alleen de eerste aanroeper kan 70 lezen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

De beschikbare hoeveelheid is (1, 1). A heeft toewijzing (1, 0) en maximum (2, 2); B heeft toewijzing (0, 1) en maximum (1, 2). Wat concludeert de veiligheidscontrole van het bankiersalgoritme?
  1. Veilig; B kan vóór A worden voltooid.
  2. Veilig; A kan vóór B worden voltooid.
  3. Onveilig; geen resterende aanspraak past aanvankelijk.
  4. Onveilig; het volledige maximum van A overschrijdt de beschikbare hoeveelheid.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een wachtoproep op een mutex met tijdslimiet geeft ETIMEDOUT terug. Wat staat daarmee vast?
  1. De houder zit in een vergrendelingscyclus.
  2. De mutex is vrijgegeven.
  3. De beschermde toestand is beschadigd.
  4. Het wachten heeft de tijdslimiet overschreden.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een server houdt 30.000 sockets open die meestal niets doen en verricht weinig rekenwerk per bericht. Waarom kan een gebeurtenislus minder geheugen per verbinding gebruiken dan een eigen thread per socket?
  1. Verbindingen hebben geen toestand nodig zolang ze niets doen.
  2. Verbindingen kunnen kleine records met opgeslagen toestand gebruiken.
  3. Elke gebeurtenisafhandelaar krijgt een afzonderlijke processorkern.
  4. Elke socket deelt één herbruikbare netwerkdescriptor.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een wachtende thread leest done zonder de mutex als onwaar. De werkthread vergrendelt daarna, maakt done waar, signaleert terwijl niemand wacht en ontgrendelt. Vervolgens vergrendelt de wachtende thread en roept wait aan zonder opnieuw te controleren. Welke afhankelijkheidsfout is opgetreden?
  1. Spontaan ontwaken vóór het signaal van de werkthread.
  2. Een veilige Mesa-hercontrole na het verwerven van de mutex.
  3. Een onthouden signaal dat op de volgende aanroeper wacht.
  4. Een verloren weksignaal na een onbeschermde controle.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

In een wachtafhankelijkheidsgraaf met bronnen met één exemplaar geldt U → V, V → W, W → V en heeft X geen uitgaande verbinding. Welke threads vormen de afhankelijkheidscyclus?
  1. V en W.
  2. U, V en W.
  3. U en X.
  4. V, W en X.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 3: Pas de regels toe wanneer condities veranderen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een gedeelde allocator heeft geen vrij geheugen. Eén wachtende thread heeft 180 bytes nodig en een andere 40. Een vrijgave voegt 90 bytes toe en signaleert alleen de thread die op 180 bytes wacht; daarna verandert er niets meer. Hoe moet het meldingsbeleid veranderen om een thread die verder kan te laten doorgaan?
  1. Wek alle wachtende threads na de vrijgave.
  2. Signaleer zodra 180 bytes beschikbaar zijn.
  3. Sla de mutex bij de wijziging over.
  4. Vervang beide while-lussen door if.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een buffer met conditievariabelen en capaciteit twee bevat één element. Twee producenten controleren elk onder de mutex of er ruimte is, ontgrendelen en vergrendelen later alleen voor put opnieuw. Beide controles zijn klaar vóór een van de put-bewerkingen. Welke toestand kunnen de twee put-bewerkingen opleveren?
  1. Teller 2; de tweede controle reserveert de plaats.
  2. Teller 1; één van de opeenvolgende put-bewerkingen wordt weggegooid.
  3. Teller 3; door de tussenruimte laten beide controles een element toe.
  4. Teller 0; de teruggesprongen index leegt de buffer.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Bij een voltooiingssemafoor wacht geen thread wanneer de werkthread een vrijgavebewerking uitvoert. Wat laat die bewerking achter voor een latere succesvolle wachtbewerking?
  1. Eén opgeslagen mutexeigenaar.
  2. Eén verloren melding.
  3. Eén geblokkeerde wachtende thread.
  4. Eén opgeslagen toegangsbewijs.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een leesvergrendeling beschermt een cache. Een lezer ziet dat een vermelding ontbreekt, geeft leestoegang vrij en wacht op schrijftoegang. Een andere schrijver vult de vermelding voordat de lezer naar binnen gaat. Wat moet de oorspronkelijke lezer doen nadat hij schrijftoegang heeft verworven?
  1. Direct invoegen op basis van de eerdere waarneming.
  2. Opnieuw controleren of de vermelding nog ontbreekt.
  3. Opnieuw leestoegang nemen terwijl hij schrijftoegang houdt.
  4. Schrijftoegang vrijgeven zonder de cache te bekijken.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Initialisatie en eerste gebruik verwerven elk dezelfde mutex, maar toch ziet het eerste gebruik soms een niet-geïnitialiseerd record. Welke garantie ontbreekt?
  1. Exclusieve toegang tijdens elke bewerking.
  2. Voltooide initialisatie vóór gebruik.
  3. Een andere eigen vergrendeling voor elke bewerking.
  4. Een vaste vertraging na elke ontgrendeling van de mutex.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Taken verwerven hun volledige verzameling bronnen terwijl ze een globale verwervingsmutex vasthouden en geven die mutex vrij voordat ze aan het werk gaan. Elk pad volgt deze regel en voor het vrijgeven van bronnen is de verwervingsmutex nooit nodig. Waarom kunnen concurrerende gedeeltelijke verwervingen geen cyclus vormen?
  1. Er kan maar één onvolledige verwervingsreeks bestaan.
  2. Geen thread kan ooit een bron vasthouden terwijl hij wacht.
  3. Alle bronnen kunnen worden afgenomen zodra het werk begint.
  4. De CPU kan een thread tijdens verwerving niet onderbreken.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Er zijn 9 identieke bronnen. A houdt er 3 vast met maximum 7; B houdt er 2 vast met maximum 4. A vraagt er nog 3. Moet het bankiersalgoritme dat verzoek toekennen?
  1. Nee; de resulterende toestand heeft geen veilige volgorde.
  2. Ja; B kan vóór A worden voltooid.
  3. Nee; het verzoek overschrijdt de resterende aanspraak van A.
  4. Ja; A kan vóór B worden voltooid.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een leesbare streamsocket geeft bij een succesvolle leesbewerking 0 terug. Welke invoertoestand moet de afhandelaar verwerken?
  1. Een tijdelijk resultaat dat anders zou blokkeren.
  2. Een ontvangen byte met waarde nul.
  3. Einde van de invoer van de andere partij.
  4. Een nog te voltooien schijfleesbewerking.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een afhandelaar plaatst een asynchrone schijfleesbewerking in de wachtrij, geeft de doelbuffer vrij en keert terug. Het verzoek loopt nog. Wat moet veranderen voordat het voltooiingspad het resultaat veilig kan gebruiken?
  1. Bewaar alleen de bestandsdescriptorwaarde tot de terugkeer.
  2. Lees direct omdat het indienen al is geslaagd.
  3. Verplaats het vrijgeven van de buffer naar vóór het indienen.
  4. Houd de buffer geldig tot de I/O is voltooid.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een lus controleert een gedeeld quotum, wacht op externe goedkeuring en verlaagt daarna het quotum. Twee taken kunnen elk de controle passeren voordat een van beide verdergaat. Welke verandering voorkomt dat beide de laatste eenheid opeisen?
  1. Maak het laden van het quotum atomair, maar houd de beslissing gesplitst.
  2. Vergrendel alleen de uiteindelijke verlaging na de goedkeuring.
  3. Reserveer atomair een quotumeenheid vóór het wachten.
  4. Reserveer na goedkeuring een eenheid op basis van de eerdere controle.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 4: Analyseer onvolledige oplossingen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een thread die op een conditie wacht, houdt mutex M vast voor zijn predicaat en houdt ook vergrendeling L vast. De wachtoproep geeft M vrij. De enige thread die het predicaat waar kan maken, moet eerst L verwerven. Wat verhindert voortgang en welke afhankelijkheid moet veranderen?
  1. De signalerende thread moet M twee keer verwerven vóór de toestandsverandering.
  2. De wachtende thread moet M gedurende het hele slapen vergrendeld houden.
  3. De wachtende thread moet het predicaat weglaten en één keer wachten.
  4. De wachtende thread moet L niet langer vasthouden tijdens dat wachten.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Vier consumenten verwerken een FIFO-wachtrij. Alle producenten zijn inmiddels beëindigd en er is op hun beëindiging gewacht, er kunnen nog echte elementen in de wachtrij staan en één gereserveerde stopmarkering laat één consument stoppen. Hoeveel markeringen moet de coördinator in de wachtrij plaatsen?
  1. 1
  2. 4
  3. 3
  4. 5
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een verzameling begint met drie toegangsbewijzen. Een lener wacht succesvol, geeft later zijn bron terug en voert per ongeluk twee keer een vrijgavebewerking uit. Hoeveel leners kunnen, nadat al het werk tot stilstand is gekomen, wachtbewerkingen passeren voordat iemand opnieuw een vrijgavebewerking uitvoert?
  1. 4
  2. 2
  3. 3
  4. 5
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een buffer met semaforen en capaciteit twee is leeg. P1 en P2 wachten elk succesvol op empty. Daarna voegt P1 onder de mutex een element in en ontgrendelt, maar heeft nog geen vrijgavebewerking op full uitgevoerd. P2 heeft nog niets ingevoegd. Een consument wacht nu op full. Wat gebeurt er?
  1. De consument verwijdert het element van P1 vóór P2 invoegt.
  2. De consument blokkeert totdat P2 zijn invoeging voltooit.
  3. De consument blokkeert totdat een toegangsbewijs voor een element beschikbaar wordt.
  4. De consument verwijdert het element van P1 zodra de mutex vrij is.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een programmeur wil een gedeelde tellerverhoging als één ondeelbare wijziging uitvoeren zonder een mutex te gebruiken. Welke atomaire bewerking implementeert die wijziging rechtstreeks?
  1. Alleen een atomaire laadbewerking.
  2. Alleen een atomaire opslagbewerking.
  3. Een conditiesignaal.
  4. Fetch-and-add.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Vijf dinerende filosofen gebruiken een semafoor die aanvankelijk op 4 is ingesteld. Ieder wacht erop, pakt een linkervork, voert een vrijgavebewerking op de semafoor uit en probeert daarna de rechtervork te pakken. Kunnen alle vijf geblokkeerd raken terwijl ieder één vork vasthoudt?
  1. Nee; het beginaantal beperkt vorkhouders blijvend tot vier.
  2. Ja; door toegangsbewijzen vroeg terug te geven kunnen alle vijf de vorkcyclus bereiken.
  3. Nee; elke vrijgavebewerking geeft de linkervork van de filosoof atomair terug.
  4. Ja; maar alleen als het beginaantal van de semafoor boven vier stijgt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

De beschikbare hoeveelheid is (1, 1). P houdt (1, 0) vast met maximum (3, 1); Q houdt (1, 1) vast met maximum (2, 2). P vraagt (0, 1). Moet het bankiersalgoritme dit verzoek toekennen?
  1. Ja; P kan met de resterende bronnen worden voltooid.
  2. Nee; de voorlopige toestand heeft geen veilige volgorde.
  3. Ja; Q kan met de resterende bronnen worden voltooid.
  4. Nee; P vraagt meer dan zijn resterende aanspraak.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een detector voor meerdere exemplaren ziet beschikbare hoeveelheid (0, 0). P houdt (1, 0) vast en vraagt (0, 1); Q houdt (0, 1) vast en vraagt (1, 0). R houdt niets vast en vraagt (1, 0). Welke houders houden de deadlock om bronnen in stand?
  1. P, Q en R.
  2. P en R.
  3. P en Q.
  4. Q en R.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een select-lus volgt aanvankelijk streamsockets 6 en 10. Alleen 6 wordt als gereed teruggegeven. De lus hergebruikt die teruggegeven verzameling zonder die opnieuw op te bouwen, terwijl 10 gegevens ontvangt en leesbewerkingen op 6 nu 0 teruggeven. Welke twee fouten kunnen volgen?
  1. Socket 10 wordt nog gevolgd; socket 6 levert een inhoudsbyte met waarde nul.
  2. Socket 10 wordt schrijfbaar; socket 6 wacht op schijfvoltooiing.
  3. Socket 10 wordt weggelaten; socket 6 kan bij invoereinde opnieuw worden afgehandeld.
  4. Socket 10 vervangt 6 in de verzameling; select sluit socket 6.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Veertig verzoeken staan in de wachtrij bij één gebeurtenislus. Elke afhandelaar voert vóór terugkeer een blokkerende schijfleesbewerking van 6 ms uit en verricht verder verwaarloosbaar weinig werk. Hoe lang wacht het laatste verzoek voordat zijn leesbewerking begint?
  1. 234 ms
  2. 240 ms
  3. 6 ms
  4. 39 ms
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 5: Pas de redenering toe op nieuwe ontwerpen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een Java-thread roept notifyAll aan op een object buiten elk synchronized-gebied en zonder op een andere manier eigenaar van de monitor ervan te zijn. Wat gebeurt er?
  1. De aanroep bewaart een melding voor latere wachtende threads.
  2. De aanroep werpt IllegalMonitorStateException.
  3. De aanroep draagt de vergrendelingen van de aanroeper over aan wachtende threads.
  4. De aanroep wacht tot de monitor vrij is.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een semafoor is geïmplementeerd met een mutex, een conditievariabele en een niet-negatieve waarde. De vrijgavebewerking verhoogt de waarde en signaleert onder de mutex. De wachtbewerking controleert de waarde met if, wacht als die nul is en verlaagt die daarna. Hoe kan dit toch misgaan?
  1. De mutex laat twee threads de waarde tegelijk veranderen.
  2. De semafoor kan geen vrijgavebewerking onthouden zonder wachtende threads.
  3. Een terugkerende wachtende thread kan een waarde van nul verlagen.
  4. Een vrijgavebewerking verlaagt de waarde altijd als niemand wacht.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een consument houdt een buffermutex vast terwijl hij op de semafoor voor beschikbare bufferelementen wacht. De buffer is leeg. Een voorgestelde oplossing laat de producent vóór het invoegen een vrijgavebewerking op die semafoor uitvoeren, zodat hij de consument zonder de mutex kan wekken. Waarom is die oplossing onveilig?
  1. Ze laat verwijdering plaatsvinden vóór de invoeging door de producent.
  2. Ze laat invoeging plaatsvinden voordat ruimte is gereserveerd.
  3. Ze geeft een toegangsbewijs voor een lege plaats terug vóór de verwijdering is voltooid.
  4. Ze maakt hetzelfde toegangsbewijs voor een voltooid element twee keer beschikbaar.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

In het basisontwerp met een lezersteller kan de eerste lezer de tellervergrendeling vasthouden terwijl hij op writelock wacht. Waarom kan de actieve schrijver toch zijn werk afmaken, anders dan een producent die door de buffermutex van een consument wordt geblokkeerd?
  1. De wachtende lezer geeft de tellervergrendeling vrij in sem_wait.
  2. De schrijver houdt de vergrendeling van de lezersteller zelf al vast.
  3. De vrijgavebewerking van de schrijver wacht tot de lezersteller nul bereikt.
  4. De schrijver kan writelock vrijgeven zonder de tellervergrendeling.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een werkthread leest een gedeelde pointer voordat de aanmakende thread klaar is met de toekenning ervan. Wat voor afhankelijkheidsfout is dit?
  1. Een volgordeschending.
  2. Een deadlock om bronnen.
  3. Uithongering van schrijvers.
  4. Een livelock bij nieuwe pogingen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Twee wijzigingslussen verwerven elk één vergrendeling, proberen tevergeefs de andere met trylock te verwerven, geven de eerste vrij en proberen het onbeperkt tegelijk opnieuw. Een voorgestelde oplossing houdt de eerste vergrendeling vast terwijl normaal op de tweede wordt gewacht. Welk risico brengt die verandering mee?
  1. De livelock blijft bestaan omdat beide threads opnieuw blijven proberen.
  2. Eén thread is gegarandeerd klaar, terwijl de andere kan worden uitgehongerd.
  3. De livelock bij nieuwe pogingen wordt mogelijk een deadlock.
  4. Beide verwervingen verlopen na elkaar onder één gemeenschappelijke eerste vergrendeling.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

De beschikbare hoeveelheid is (1, 1). P houdt (1, 0) vast met maximum (2, 1); Q houdt (0, 2) vast met maximum (1, 3); R houdt (1, 0) vast met maximum (3, 2). In welke veilige volgorde kunnen alle drie worden voltooid?
  1. R, P, Q.
  2. P, R, Q.
  3. Q, R, P.
  4. P, Q, R.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

De beschikbare hoeveelheid is (0, 0). A houdt (1, 0) vast, vraagt momenteel (0, 0) en heeft resterende maximale behoefte (0, 2). B houdt (0, 2) vast en vraagt momenteel (1, 0). Vindt detectie op basis van huidige verzoeken een deadlock?
  1. Ja; geen van beide maximale behoeften past binnen de aanvankelijk beschikbare hoeveelheid.
  2. Nee; A geeft (1, 0) vrij, zodat het verzoek van B verder kan.
  3. Ja; aan het huidige verzoek van B kan niet direct worden voldaan.
  4. Nee; beide verzoeken passen al binnen de aanvankelijk beschikbare hoeveelheid.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een ontwikkelaar vervangt het herhaaldelijk doorzoeken van lopende I/O-verzoeken door een voltooiingswachtrij. Welk werk kan de lus nu direct laten uitvoeren?
  1. Definitieve antwoorden voor elk nieuw ingediend verzoek.
  2. Vervolgwerk voor bewerkingen die als voltooid zijn gemeld.
  3. Leesbewerkingen op elk gewoon bestand dat select als gereed meldt.
  4. Parallelle uitvoering van alle afhandelaars op één lusthread.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een ring met capaciteit twee begint leeg. Plaats 31, plaats 32, haal één keer op en plaats 33. Beide indices zijn nu 1. Wat zijn de bezettingsteller en de waarde die de volgende ophaalbewerking teruggeeft?
  1. Teller 1; volgende waarde 32.
  2. Teller 2; volgende waarde 33.
  3. Teller 0; ophalen moet wachten.
  4. Teller 2; volgende waarde 32.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

90 Kernbegrippen, elk met een definitie

A

Asynchrone I/O
Asynchrone I/O scheidt het indienen van een bewerking van het ophalen van het voltooide resultaat, zodat de aanroeper intussen ander werk kan afhandelen.
De afhandelaar dient een bestandsleesbewerking in en stuurt het antwoord nadat de voltooiing binnenkomt.
Atomair vrijgeven en wachten
Atomair vrijgeven en wachten maakt van het vrijgeven van de mutex en het beginnen met wachten op een conditievariabele één ondeelbare overdracht aan concurrerende threads.
Het kind kan niet signaleren in een tussenruimte nadat de ouder ontgrendelt maar voordat die begint te wachten.

B

Bankiersalgoritme
Het bankiersalgoritme kent een bronverzoek alleen toe wanneer het binnen de opgegeven resterende aanspraken past en een veilige voltooiingsvolgorde behoudt.
Een allocator kent een verzoek voorlopig toe, toetst de resulterende toestand en stelt een onveilige toekenning uit.
Begrensde buffer
Een begrensde buffer is een gedeelde wachtrij met een vaste capaciteit die producenten laat wachten wanneer de buffer vol is en consumenten wanneer die leeg is.
Een buffer met capaciteit drie kan geen vierde element opnemen totdat een consument er één verwijdert.
Begrenzing van gelijktijdige toegang
Begrenzing van gelijktijdige toegang beperkt hoeveel threads tegelijk een kostbare fase ingaan, zodat hun gezamenlijke behoefte binnen een gekozen budget aan bronnen blijft.
Twaalf gelijktijdige werkthreads die elk 500 MB gebruiken, verbruiken 6.000 MB tijdens hun geheugenintensieve fase.
Beschikbaarheidsvector
Een beschikbaarheidsvector telt de momenteel niet-toegewezen eenheden van elk brontype en vormt de beginwaarde van de gesimuleerde beschikbaarheid voor een veiligheidscontrole.
Trek alle vastgehouden eenheden van elk brontotaal af voordat je de voltooiingssimulatie begint.
Besturingsblok voor asynchrone I/O
Een besturingsblok voor asynchrone I/O beschrijft een POSIX-AIO-verzoek, inclusief descriptor, bestandspositie, buffer en aangevraagd aantal bytes.
De aiocb en de doelbuffer blijven geldig zolang de leesbewerking in de wachtrij nog openstaat.
Binaire semafoor
Een binaire semafoor die als vergrendeling wordt gebruikt, begint met één toegangsbewijs en combineert verwerving vóór een kritieke sectie met vrijgave erna.
Een tweede thread die naar binnen wil, blokkeert totdat de eerste het toegangsbewijs voor de kritieke sectie teruggeeft.
Buffermutex
Een buffermutex laat wijzigingen aan de bufferarray en indices na elkaar verlopen, onafhankelijk van semaforen die beschikbare bronnen tellen.
Twee producenten met geldige toegangsbewijzen voor ruimte wijzigen de schrijfindex van de buffer, fill_ptr, nog steeds onder dezelfde mutex.

C

Circulair wachten
Circulair wachten is een gesloten keten van threads waarin elke thread wacht op een bron die de volgende thread in de keten vasthoudt.
Drie threads die van A op B, van B op C en van C op A wachten, sluiten de afhankelijkheidscyclus.
Compare-and-swap
Compare-and-swap vervangt atomair de waarde op een locatie, maar alleen wanneer de huidige waarde overeenkomt met de door de aanroeper opgegeven verwachte waarde.
Een tellerbijwerker probeert het opnieuw wanneer een andere thread de oude waarde verandert voordat zijn CAS slaagt.
Conditievariabele
Een conditievariabele biedt een wachtrij waarop threads slapen totdat een verandering in de gedeelde toestand hen hun conditie opnieuw laat proberen.
Een consument wacht op een conditievariabele wanneer de verzoekenwachtrij geen werk bevat.
Controleren en vervolgens handelen
Controleren en vervolgens handelen is een bewerking die gedeelde toestand controleert en die daarna verandert of gebruikt op een manier die ervan afhangt dat de controle geldig blijft.
Voorraad controleren en reserveren moet bijeen blijven wanneer andere threads hetzelfde artikel kunnen verkopen.

D

Deadlock
Een deadlock laat een verzameling geblokkeerde threads vastlopen omdat elk een handeling of bron nodig heeft die alleen een ander vastgelopen lid kan bieden.
T1 houdt A vast en wacht op B terwijl T2 B vasthoudt en op A wacht.
Deadlockdetectie
Deadlockdetectie doorzoekt huidige wachtafhankelijkheden of bronverzoeken op een bestaande verzameling die geen voortgang kan maken.
Een zoekopdracht naar cycli vindt drie mutexhouders die op elkaar blokkeren.
Deadlockpreventie
Deadlockpreventie ontwerpt de verwerving van bronnen zo dat ten minste één noodzakelijke voorwaarde voor deadlock nooit kan ontstaan.
Een gemeenschappelijke volgorde voor rekeningvergrendelingen voorkomt dat overboekingen in tegengestelde richtingen circulair wachten veroorzaken.
Deadlockvermijding
Deadlockvermijding toetst voorgestelde brontoekenningen aan vooraf bekende behoeften en stelt toekenningen uit die het systeem onveilig zouden maken.
Een vrije bron kan worden achtergehouden wanneer toekenning elke veilige voltooiingsvolgorde zou wegnemen.
Descriptorverzameling
Een descriptorverzameling geeft bestandsdescriptornummers weer die voor bewaking zijn geselecteerd, met bits die lidmaatschap of teruggegeven gereedheid aangeven.
De server bouwt zijn leesverzameling opnieuw op omdat select() die vervangt door de deelverzameling die gereed is.

E

Eerlijke toelating
Eerlijke toelating verleent toegang volgens beleid dat voorkomt dat een voortdurende groep aankomers een wachtende thread onbeperkt passeert.
Eerlijk lezers-schrijversbeleid voorkomt dat latere lezers een eerdere schrijver eindeloos voorbijgaan.
Eigenaarschap van een semafoor
Een semafoor heeft geen eigenaar, zodat elke thread een toegangsbewijs kan vrijgeven, ook als een andere thread de bijbehorende wachtbewerking heeft uitgevoerd.
Een kind voert een vrijgavebewerking uit op een voltooiingssemafoor waarop zijn ouder wacht.
epoll
epoll is een Linux-gebeurtenisinterface die een verzameling te volgen descriptoren in de kernel bewaart en gereedstaande gebeurtenissen voor geregistreerde descriptoren meldt.
Een server kan veel verbindingen bewaken zonder telkens de volledige gevolgde lijst opnieuw in te dienen.

F

Fetch-and-add
Fetch-and-add is een atomaire bewerking van lezen-wijzigen-schrijven die een hoeveelheid optelt en de oude waarde teruggeeft zonder tussenliggende wijziging.
Een atomaire fetch-and-add voorkomt dat gelijktijdige tellerverhogingen elkaar overschrijven.

G

Gebeurtenislus
Een gebeurtenislus wacht op gebeurtenissen en laat korte afhandelaars uitvoeren, zodat één thread veel taken kan coördineren waarvan de wachtperioden overlappen.
Een server handelt gegevens van één gereedstaande socket af en keert daarna terug om een andere verbinding te bedienen.
Geen gedwongen ontneming
Geen gedwongen ontneming betekent dat een vastgehouden bron niet gedwongen van de houder kan worden afgenomen en volgens het bijbehorende protocol moet worden vrijgegeven.
Een thread die op een gewone mutex wacht, kan de vergrendeling niet zomaar van de huidige eigenaar afnemen.
Gegevenseindmarkering
Een gegevenseindmarkering is een gereserveerd wachtrijelement dat een consument laat stoppen nadat alle voorafgaande echte elementen zijn afgehandeld.
Een coördinator wacht op de beëindiging van elke producent voordat die per consument één stopmarkering invoegt.
Gereedheidsvlag
Een gereedheidsvlag legt onder synchronisatie voltooide initialisatie vast, zodat gebruikers kunnen wachten totdat de vereiste toestand bestaat.
De aanmakende thread stelt ready pas in nadat die het record heeft toegekend dat de werkthread zal gebruiken.

H

Handmatig stackbeheer
Handmatig stackbeheer bewaart lokale taaktoestand expliciet tussen terugkerende afhandelaars, in plaats van te vertrouwen op één opgeschorte aanroepstack.
Een antwoordrecord bewaart de positie en bestemming van het verzoek totdat de leesbewerking klaar is.
Hoare-semantiek
Hoare-semantiek draagt de vergrendeling en de uitvoering rechtstreeks over aan een wachtende thread die een melding ontvangt, zodat de gesignaleerde conditie bij die overdracht blijft gelden.
De thread die een melding kreeg, gaat verder voordat een andere binnentredende thread het nieuw beschikbare element kan pakken.
Hybride gelijktijdigheid
Hybride gelijktijdigheid combineert een gebeurtenislus met werkthreads of werkprocessen die blokkerende of CPU-intensieve bewerkingen buiten de lusthread uitvoeren.
Een netwerklus stuurt beeldverwerking naar werkthreads en handelt later hun voltooiingen af.

I

I/O-gereedheid
I/O-gereedheid meldt dat een I/O-bewerking voor een descriptor kan worden geprobeerd; ze draagt de gegevens niet over naar het programma.
Een leesbare socket kan bij het lezen inhoudsgegevens teruggeven of het einde van de invoer melden.
Impliciete blokkering
Impliciete blokkering vertraagt een thread door een bewerking die direct lijkt te verlopen, zoals toegang tot een pagina die van schijf moet worden gelezen.
Een afhandelaar loopt vast op een paginafout, ook al voert die geen expliciete blokkerende leesaanroep uit.
io_uring
io_uring is een Linux-interface met indienings- en voltooiingswachtrijen voor asynchrone bewerkingen die I/O-afhandeling op basis van voltooiing ondersteunt.
Een server dient verzoeken in en laat later voortzettingen uitvoeren op basis van hun voltooiingsrecords.

K

kqueue
kqueue is een interface voor gebeurtenismeldingen op FreeBSD en macOS die geregistreerde interesses bijhoudt en gemelde gebeurtenissen teruggeeft.
Een server registreert te volgen sockets bij kqueue in plaats van volledige select-descriptorverzamelingen opnieuw op te bouwen.

L

Lezers-schrijversvergrendeling
Een lezers-schrijversvergrendeling laat meerdere lezers de toegang delen of één schrijver exclusieve toegang tot beschermde gegevens hebben.
Meerdere zoekopdrachten in een cache kunnen overlappen, maar voor het wijzigen van de cache is schrijftoegang nodig.
Lezersvoorrang
Lezersvoorrang laat aankomende lezers zich bij een actieve lezersgroep voegen, ook wanneer al een schrijver op toegang wacht.
Overlappende lezers kunnen de lezersteller boven nul houden terwijl een schrijver wacht.
Livelock
Een livelock laat threads draaien en op elkaar reageren terwijl hun herhaalde handelingen geen nuttig werk voltooien.
Twee trylock-lussen geven herhaaldelijk tegelijk vrij en proberen het opnieuw zonder dat een van beide beide vergrendelingen verkrijgt.

M

Mesa-semantiek
Mesa-semantiek maakt een thread die een melding ontvangt gereed om mee te dingen naar de vergrendeling, zodat die de conditie opnieuw moet controleren nadat hij de vergrendeling opnieuw heeft verworven.
Een tweede consument kan het enige element pakken voordat de eerste consument die een melding kreeg de mutex terugkrijgt.
Model met een teller met teken
Het model met een teller met teken stelt geblokkeerde threads die op een semafoor wachten voor met negatieve waarden, terwijl het voor threads zichtbare gedrag van wacht- en vrijgavebewerkingen gelijk blijft.
Een waarde van −2 in een uitvoeringsverloop staat voor twee geblokkeerde wachtende threads, niet voor een negatief aantal toegangsbewijzen in Linux.
Monitor
Een monitor groepeert gedeelde toestand met bewerkingen die één thread tegelijk toelaten en met de mogelijkheid om op een conditie te wachten.
Een synchronized-blok in Java houdt de objectmonitor vast die nodig is voor aanroepen van wait().

N

Niet-blokkerende I/O
Niet-blokkerende I/O geeft de besturing terug wanneer een bewerking niet direct verder kan, zodat de aanroepende thread ander werk kan afhandelen.
Een leesbewerking op een socket geeft EAGAIN terug en de afhandelaar keert terug naar de gebeurtenislus.
Niet-blokkerende vergrendelingspoging
Een niet-blokkerende vergrendelingspoging probeert een mutex te verwerven en keert direct terug als die niet beschikbaar is, zodat de aanroeper kan vrijgeven en opnieuw proberen.
Een mislukte tweede trylock laat de overboeking de eerste rekeningvergrendeling vrijgeven.

O

Omzetten van een leesvergrendeling in een schrijfvergrendeling
Het omzetten van een leesvergrendeling in een schrijfvergrendeling verandert leestoegang in exclusieve schrijftoegang; in het basisontwerp moet de leestoegang eerst worden vrijgegeven.
Na het verwerven van schrijftoegang controleert een cachebijwerker opnieuw of de vermelding nog moet worden gewijzigd.
Onveilige toestand
Een onveilige toestand heeft geen gegarandeerde voltooiingsvolgorde bij maximale behoeften, hoewel werkelijk toekomstig gedrag een deadlock nog kan vermijden.
Een thread kan bronnen vroeg vrijgeven in plaats van zijn volledige resterende aanspraak aan te vragen.
Opschorting
Een opschorting pauzeert één functie totdat een bewerking waarop wordt gewacht klaar is, terwijl de gebeurtenislus ander gereedstaand werk kan uitvoeren.
Een async-functie gaat verder nadat de leesbewerking van de functie is voltooid, met behoud van de lokale variabelen.
Overkoepelende conditie
Een overkoepelende conditie gebruikt een brede melding om elke wachtende thread te bereiken waarvan de specifiekere conditie nu mogelijk is vervuld.
Wanneer een allocator met wachtende aanvragers voor 10 en 100 bytes 50 bytes vrijgeeft, wekt die beide zodat elk opnieuw kan controleren.

P

Parallellisme op meerdere processorkernen
Parallellisme op meerdere processorkernen voert berekeningen tegelijk op meerdere CPU-kernen uit en vereist meer uitvoerende threads of processen dan één lusthread.
Meerdere werkthreads voor beeldverwerking kunnen tegelijk rekenen terwijl één gebeurtenislus sockets afhandelt.
poll
poll() bewaakt een array met descriptorvermeldingen op aangevraagde I/O-gebeurtenissen zonder de vaste beperking van descriptorwaarden van fd_set.
Een hoog genummerde socket kan met poll worden gevolgd, ook als de waarde niet in een select-verzameling past.
Pool van werkthreads of werkprocessen
Een pool van werkthreads of werkprocessen is een begrensde groep herbruikbare threads of processen die ingediende taken uitvoeren en hun resultaten teruggeven.
libuv laat bestandssysteembewerkingen door zijn threadpool uitvoeren terwijl de lus gebeurtenissen bedient.
Predicaat
Een predicaat is een voorwaarde op de gedeelde toestand die waar of onwaar is en die een thread controleert voordat hij beslist of hij verder kan.
Met het predicaat done != 0 kan een ouder controleren of zijn kind klaar is.
Producent-consumentprobleem
Het producent-consumentprobleem betreft de coördinatie van threads die elementen toevoegen aan en verwijderen uit een gedeelde buffer met beperkte capaciteit.
Een netwerkontvanger produceert verzoeken die werkthreads uit een begrensde wachtrij consumeren.

R

Rendez-vous
Een rendez-vous is een ontmoetingspunt dat geen van twee threads mag passeren voordat beide hebben aangekondigd dat ze zijn aangekomen.
Elke thread meldt zijn aankomst met een vrijgavebewerking voordat hij op het toegangsbewijs voor de aankomst van de andere thread wacht.
Reservering van een bron
Een reservering van een bron wijst één eenheid van een bron toe aan één toegelaten bewerking, ook voordat die bewerking de gedeelde administratie wijzigt.
Een succesvolle wachtbewerking van een producent op ruimte reserveert een plaats terwijl hij op de buffermutex wacht.
Ringbuffer
Een ringbuffer slaat een wachtrij op in een array waarvan de lees- en schrijfindex van de laatste plaats terugspringen naar de eerste.
Na het schrijven naar plaats 2 van een array met drie plaatsen springt de volgende schrijfpositie terug naar plaats 0.

S

Schending van atomiciteit
Een schending van atomiciteit laat een andere thread toegangen onderbreken die zich als één ondeelbare bewerking moeten gedragen.
Twee afzonderlijk vergrendelde gebieden rond een saldocontrole en afschrijving laten nog steeds een onveilige tussenruimte bestaan.
Schrijversvoorrang
Schrijversvoorrang blokkeert nieuwe lezers wanneer een schrijver wacht, waardoor schrijvers minder worden uitgesteld maar lezers mogelijk worden uitgehongerd.
Een aankomende zoekopdracht wacht achter de cacheschrijver in plaats van zich bij de huidige lezersgroep te voegen.
select
select() bewaakt descriptorverzamelingen op I/O-gereedheid, vervangt ze door de gereedstaande deelverzamelingen en beperkt de controle tot descriptornummers onder nfds.
Voor het volgen van descriptoren 5 en 12 zijn nfds 13 en het opnieuw opbouwen van de gevolgde verzamelingen na elke aanroep nodig.
Semafoor
Een semafoor is een synchronisatieobject op basis van een geheel getal met atomaire wacht- en vrijgavebewerkingen die toegangsbewijzen verwerven en beschikbaar maken.
Een vrijgavebewerking kan vóór een wachtbewerking plaatsvinden omdat de semafoor het ongebruikte toegangsbewijs onthoudt.
Semafoor voor beschikbare bufferelementen
Een semafoor voor beschikbare bufferelementen telt beschikbaar gemaakte bufferelementen die consumenten kunnen reserveren voordat ze die uit de gedeelde array verwijderen.
Een aanvankelijk lege buffer geeft zijn semafoor voor beschikbare bufferelementen nul toegangsbewijzen.
Semafoor voor vrije bufferplaatsen
Een semafoor voor vrije bufferplaatsen telt toegangsbewijzen waarmee producenten beschikbare bufferplaatsen kunnen reserveren voordat ze hun voltooide elementen invoegen.
Een lege buffer met capaciteit vijf initialiseert zijn semafoor voor vrije bufferplaatsen op vijf.
Signaal
Een signaal geeft een melding aan ten minste één thread die op dat moment op een conditievariabele wacht, als er wachtende threads zijn, zonder een melding voor latere aankomers op te slaan.
Een kind signaleert nadat het zijn beschermde voltooiingsvlag heeft ingesteld, zodat een wachtende ouder die opnieuw kan controleren.
Spontaan ontwaken
Spontaan ontwaken is terugkeer uit een wachtoproep op een conditievariabele zonder bijbehorende melding, waarna dezelfde hercontrole van het predicaat nodig is als bij elk weksignaal.
Een consument gaat in de lus terug naar de wachtoproep als hij ontwaakt maar de buffer nog steeds leeg vindt.

T

Taak die voor herstel wordt afgebroken of teruggedraaid
Een taak die voor herstel wordt afgebroken of teruggedraaid is geselecteerd werk dat een systeem afbreekt of terugdraait om bronnen vrij te geven en een gedetecteerde deadlock te doorbreken.
Een proces dat weinig werk heeft voltooid kan goedkoper opnieuw te starten zijn dan een proces dat bijna klaar is.
Telsemafoor
Een telsemafoor begint met het aantal beschikbare identieke eenheden en begrenst hoeveel gebruikers die tegelijk kunnen verwerven.
Een verzameling verbindingen met vier toegangsbewijzen laat een vijfde lener op een teruggegeven verbinding wachten.
Terugdraaien
Terugdraaien herstelt een eerder geldig checkpoint, zodat werk bronnen kan vrijgeven of terugwinnen zonder gedeeltelijke wijzigingen te behouden.
Een transactiesysteem breekt een transactie in deadlock af en probeert die opnieuw via zijn terugdraaimechanisme.
Time-out
Een time-out beëindigt een wachtperiode na een tijdslimiet en maakt buitensporige vertraging zichtbaar zonder een permanente deadlock van trage voortgang te onderscheiden.
Een verstreken wachtperiode op een mutex met tijdslimiet kan zowel door een overbelast systeem als door een vergrendelingscyclus komen.
Toegangsbewijs
Een toegangsbewijs vertegenwoordigt één beschikbare toelating of eenheid van een bron die een succesvolle wachtbewerking op een semafoor gebruikt en een vrijgavebewerking beschikbaar maakt.
Een semafoor met drie ongebruikte toegangsbewijzen kan drie threads toelaten zonder ze te blokkeren.
Toelatingsregeling
Een toelatingsregeling bepaalt of meer werk een gebied met beperkte bronnen mag ingaan en stelt dat werk uit wanneer de toegestane capaciteit in gebruik is.
Een werkthread wacht op een toegangsbewijs voor verwerking voordat hij zijn grote tijdelijke buffer toewijst.
Toestandsvariabele
Een toestandsvariabele legt een feit over gedeeld werk vast, zodat een thread het kan controleren ook nadat een eerdere melding voorbij is gegaan.
Een voltooiingsvlag blijft ingesteld wanneer het kind klaar is voordat de ouder begint te wachten.

U

Uithongering
Uithongering stelt één thread onbeperkt uit terwijl andere nuttig werk blijven verrichten, ook al laat geen geblokkeerde afhankelijkheidscyclus ze allemaal vastlopen.
Een schrijver blijft wachten terwijl een onafgebroken stroom overlappende lezers de bron gebruikt.
Uithongering van schrijvers
Uithongering van schrijvers is het onbeperkt uitstellen van een wachtende schrijver terwijl lezers blijven binnenkomen en voortgang maken.
Een vergrendeling die alleen lezers telt, geeft een schrijver geen beurt als nieuwe lezers elkaar voortdurend overlappen.

V

Vaste vergrendelingsvolgorde
Een vaste vergrendelingsvolgorde kent een gemeenschappelijke verwervingsvolgorde toe aan vergrendelingen en vereist dat elk relevant codepad die volgt.
Beide richtingen van een overboeking vergrendelen de rekening met het laagste nummer vóór die met het hoogste nummer.
Vasthouden en wachten
Vasthouden en wachten ontstaat wanneer een thread bronnen die hij al vasthoudt behoudt terwijl hij blokkeert voor extra bronnen die hij nodig heeft.
Een overboeking houdt één rekeningvergrendeling vast terwijl de thread wacht om de andere te verwerven.
Vector van gesimuleerde beschikbaarheid
Een vector van gesimuleerde beschikbaarheid geeft de gesimuleerde beschikbaarheid in de veiligheidscontrole weer en wordt met de toewijzing van een thread verhoogd wanneer die als voltooid wordt gemarkeerd.
Het voltooien van een thread die (2, 1) vasthoudt, verandert de gesimuleerde beschikbaarheid (1, 3) in (3, 4).
Vector van resterende behoeften
Een vector van resterende behoeften legt de resterende maximale aanspraak van een thread voor elk brontype vast, berekend als het maximum min de huidige toewijzing.
Een thread met maximum (4, 3) en toewijzing (1, 2) heeft resterende behoefte (3, 1).
Veilige toestand
Een veilige toestand heeft ten minste één voltooiingsvolgorde die met beschikbare en vrijgegeven bronnen aan de resterende maximale behoefte van elke thread kan voldoen.
Eén thread kan eerst worden voltooid en genoeg bronnen vrijgeven om daarna een tweede thread te laten voltooien.
Veilige volgorde
Een veilige volgorde ordent alle threads zo dat elke resterende maximale aanspraak past binnen de bronnen die beschikbaar zijn nadat eerdere threads zijn voltooid.
Elke stap van een voorgestelde volgorde moet voor elk brontype passen, niet alleen voor het totale aantal.
Verloren weksignaal
Een verloren weksignaal ontstaat wanneer een benodigde melding voorbijgaat voordat een thread begint te wachten, waardoor die blijft slapen zonder toekomstige melding.
Wanneer een ouder een voltooiingsvlag buiten de mutex controleert, kan het kind tussen de controle en het wachten klaar zijn.
Verloren wijziging
Een verloren wijziging ontstaat wanneer overlappende bewerkingen van lezen-wijzigen-schrijven elkaar overschrijven, zodat een bedoelde verandering verdwijnt.
Twee threads laden tellerwaarde 8 en slaan elk 9 op, hoewel twee verhogingen 10 zouden moeten opleveren.
Vervlechting van uitvoeringsstappen
Een vervlechting van uitvoeringsstappen is een ordening van stappen uit verschillende threads die de eigen volgorde van elke thread behoudt terwijl hun toegangen worden gemengd.
Een pointer op null zetten tussen de controle en het gebruik door een andere thread legt de onveilige uitvoeringsvolgorde bloot.
Volgordeschending
Een volgordeschending ontstaat wanneer een bewerking vóór een vereiste eerdere bewerking wordt uitgevoerd doordat hun afhankelijkheid niet wordt afgedwongen.
Een werkthread leest configuratie voordat de laadthread klaar is met het beschikbaar maken van de waarden.
Voltooiingswachtrij
Een voltooiingswachtrij levert records voor voltooide asynchrone bewerkingen, zodat een programma resultaten kan ophalen en vervolgwerk kan laten uitvoeren.
De lus leest een voltooiingsrecord in plaats van elke openstaande leesbewerking herhaaldelijk te controleren.
Voortzetting
Een voortzetting slaat de toestand op die nodig is om een taak te hervatten nadat een afhandelaar is teruggekeerd terwijl die op een asynchrone bewerking wacht.
Het leesverzoek slaat op welke clientsocket de gegevens moet ontvangen.
Vrijgavebewerking op een semafoor
Een vrijgavebewerking op een semafoor maakt atomair één toegangsbewijs beschikbaar, zodat een geblokkeerde wachtende thread kan doorgaan of de beschikbaarheid voor een latere wachtbewerking wordt bewaard.
Een werkthread voert een vrijgavebewerking uit op zijn voltooiingssemafoor, ook als zijn ouder nog niet is begonnen met wachten.

W

Waakhondtimer
Een waakhondtimer activeert herstel wanneer software binnen een tijdslimiet geen voortgang meldt, zonder de oorzaak eenduidig te diagnosticeren.
Een ingebedde besturing wordt gereset nadat de software stopt met het sturen van de verwachte voortgangsmeldingen.
Wachtafhankelijkheidsgraaf
Een wachtafhankelijkheidsgraaf tekent threads als knopen en wachtafhankelijkheden als gerichte verbindingen van de wachtende thread naar een bronhouder.
Een pijl van T1 naar T2 betekent dat T1 een bron nodig heeft die T2 momenteel vasthoudt.
Wachtbewerking op een semafoor
Een wachtbewerking op een semafoor verwerft atomair één toegangsbewijs en blokkeert als er geen beschikbaar is totdat een andere bewerking er één beschikbaar maakt.
Een lener wacht voordat die een handle neemt, zodat de toelatingslimiet van de verzameling niet wordt overschreden.
Wachten op threadbeëindiging
Wachten op threadbeëindiging laat een thread wachten tot een specifieke andere thread is beëindigd voordat hij verdergaat met werk waarvoor die voltooiing nodig is.
De rapportagethread wacht op de beëindiging van de optellende werkthread voordat hij diens eindtotaal leest.
Wederzijdse uitsluiting
Wederzijdse uitsluiting laat slechts één thread tegelijk een bron vasthouden of een beschermde bewerking op gedeelde toestand uitvoeren.
Een exclusieve mutex kan niet aan een tweede thread worden toegekend terwijl de eigenaar die nog vasthoudt.
Wekken van alle wachtende threads
Het wekken van alle wachtende threads op een conditievariabele wekt alle threads die er op dat moment op wachten; elk moet de mutex opnieuw verwerven en het eigen predicaat controleren.
Een allocator wekt na een vrijgave alle wachtende threads omdat ze verschillende hoeveelheden geheugen aanvragen.
Willekeurige wachttijd vóór een nieuwe poging
Een willekeurige wachttijd vóór een nieuwe poging voegt een willekeurig gekozen vertraging toe voordat opnieuw wordt geprobeerd, zodat threads die anders gelijk opgaan minder vaak herhaaldelijk botsen.
Overboekingen die het opnieuw proberen wachten verschillende intervallen, zodat ze minder kans hebben opnieuw te botsen.

Over deze les

Door StudeliFout melden

Maak je eigen Studiemateriaal

Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.

Aan de slag