Vraag C ++ 11 introduceerde een gestandaardiseerd geheugenmodel. Wat betekent het? En hoe gaat het de C ++ -programmering beïnvloeden?


C ++ 11 introduceerde een gestandaardiseerd geheugenmodel, maar wat betekent dat precies? En hoe gaat het de C ++ -programmering beïnvloeden?

Dit artikel (door Gavin Clarke wie citeert Herb Sutter) zegt dat,

Het geheugenmodel betekent die C ++ -code   heeft nu een gestandaardiseerde bibliotheek om te bellen   ongeacht wie de compiler heeft gemaakt   en op welk platform het draait.   Er is een standaardmanier om te bepalen hoe   verschillende threads praten met de   geheugen van de processor.

"Als je het hebt over splijten   [code] over verschillende kernen dat is   in de standaard hebben we het over   het geheugenmodel. Wij gaan   optimaliseren zonder de   volgende aannames gaan mensen   maken in de code, " Sutter zei.

Nou ik kan memoriseren deze en soortgelijke paragrafen online beschikbaar (aangezien ik sinds mijn geboorte mijn eigen geheugenmodel heb: P) en zelfs kunnen plaatsen als een antwoord op vragen van anderen, maar om eerlijk te zijn begrijp ik dit niet helemaal.

Dus, wat ik eigenlijk wil weten, is dat C ++ -programmeurs voorheen al multithread-applicaties ontwikkelden, dus hoe maakt het uit of het POSIX-threads of Windows-threads of C ++ 11-threads zijn? Wat zijn de voordelen? Ik wil de details op laag niveau begrijpen.

Ik krijg ook het gevoel dat het C ++ 11-geheugenmodel op een of andere manier gerelateerd is aan C ++ 11 multi-threading ondersteuning, omdat ik deze twee vaak samen zie. Als dat zo is, hoe precies? Waarom zouden ze familie zijn?

Omdat ik niet weet hoe de interne functies van multithreading werken en welk geheugenmodel in het algemeen betekent, help me me deze concepten te begrijpen. :-)


1550
2018-06-11 23:30


oorsprong


antwoorden:


Ten eerste moet je leren denken als een taaladvocaat.

De C ++ -specificatie verwijst niet naar een bepaalde compiler, besturingssysteem of CPU. Het verwijst naar een abstracte machine dat is een generalisatie van echte systemen. In de taaladvocatenwereld is het de taak van de programmeur om code te schrijven voor de abstracte machine; de taak van de samensteller is om die code op een concrete machine te actualiseren. Door rigide te coderen naar de specificatie, kunt u er zeker van zijn dat uw code zal worden gecompileerd en zonder aanpassingen zal worden uitgevoerd op elk systeem met een compliante C ++ -compiler, of vandaag of over 50 jaar.

De abstracte machine in de C ++ 98 / C ++ 03-specificatie is fundamenteel enkelvoudig van schroefdraad voorzien. Het is dus niet mogelijk om multi-threaded C ++ code te schrijven die "volledig draagbaar" is ten opzichte van de specificatie. De specificatie zegt zelfs niets over de valentie van geheugenbelastingen en winkels of de bestellen in welke ladingen en winkels kunnen gebeuren, laat staan ​​dingen als mutexes.

Natuurlijk kunt u multi-threaded code in de praktijk schrijven voor specifieke betonsystemen, zoals pthreads of Windows. Maar er is geen standaard-manier om multi-threaded code te schrijven voor C ++ 98 / C ++ 03.

De abstracte machine in C ++ 11 is multi-threaded door design. Het heeft ook een goed gedefinieerde geheugen model; dat wil zeggen, het zegt wat de compiler wel en niet mag doen als het gaat om toegang krijgen tot het geheugen.

Bekijk het volgende voorbeeld, waarbij een paar algemene variabelen gelijktijdig worden gebruikt door twee threads:

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

Wat kan Thread 2 uitvoeren?

Onder C ++ 98 / C ++ 03 is dit niet eens Undefined Behavior; de vraag zelf is zinloos omdat de standaard geen rekening houdt met iets dat een "draad" wordt genoemd.

Onder C ++ 11 is het resultaat Undefined Behavior, omdat belastingen en winkels in het algemeen niet atomair hoeven te zijn. Wat misschien geen verbetering lijkt ... En dat is het zelf ook niet.

Maar met C ++ 11 kun je dit schrijven:

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

Nu wordt het veel interessanter. Allereerst is het gedrag hier bepaald. Discussie 2 kon nu afdrukken 0 0 (als het vóór Thread 1 loopt), 37 17 (als het achter Thread 1 loopt), of 0 17 (als het wordt uitgevoerd nadat Thread 1 is toegewezen aan x maar voordat het wordt toegewezen aan y).

Wat het niet kan afdrukken is 37 0, omdat de standaardmodus voor atomaire belastingen / winkels in C ++ 11 is om af te dwingen sequentiële consistentie. Dit betekent gewoon dat alle ladingen en winkels "alsof" moeten zijn, in de volgorde waarin u ze in elke thread hebt geschreven, terwijl bewerkingen tussen threads kunnen worden doorgeschoten, maar het systeem houdt van. Dus het standaardgedrag van atomics biedt beide valentie en bestellen voor ladingen en winkels.

Nu, op een moderne CPU, kan het waarborgen van sequentiële consistentie duur zijn. In het bijzonder zal de compiler waarschijnlijk volledige geheugenbarrières uitstoten tussen elke toegang hier. Maar als uw algoritme niet-gespecificeerde belastingen en winkels kan verdragen; d.w.z. als het atomiciteit vereist, maar niet ordening; d.w.z., als het kan verdragen 37 0 als uitvoer van dit programma, dan kun je dit schrijven:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

Hoe moderner de CPU, hoe groter de kans dat deze sneller is dan in het vorige voorbeeld.

Als u ten slotte bepaalde ladingen en winkels op volgorde wilt houden, kunt u schrijven:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

Dit brengt ons terug naar de bestelde ladingen en winkels - dus 37 0 is niet langer een mogelijke uitvoer - maar het doet dit met minimale overhead. (In dit triviale voorbeeld is het resultaat hetzelfde als volledige consistente consistentie, in een groter programma zou dat niet het geval zijn.)

Natuurlijk, als de enige outputs die u wilt zien zijn 0 0 of 37 17, je kunt gewoon een mutex rond de originele code wikkelen. Maar als je dit tot nu toe hebt gelezen, weet ik zeker dat je al weet hoe dat werkt, en dit antwoord is al langer dan ik van plan was :-).

Dus, bottom line. Mutexes zijn geweldig en C ++ 11 standaardiseert ze. Maar soms wilt u om prestatieredenen primitieven op een lager niveau (bijvoorbeeld de klassieker dubbel gecontroleerd vergrendelingspatroon). De nieuwe standaard biedt geavanceerde gadgets zoals mutexen en toestandsvariabelen en biedt ook gadgets op laag niveau zoals atomaire typen en de verschillende smaken van geheugenbarrière. U kunt nu dus geavanceerde, krachtige gelijktijdige routines schrijven, volledig binnen de taal die door de standaard wordt gespecificeerd, en u kunt er zeker van zijn dat uw code zal worden gecompileerd en onveranderd zal blijven werken op zowel de systemen van vandaag als die van morgen.

Hoewel om eerlijk te zijn, zou je waarschijnlijk moeten vasthouden aan mutexes en conditievariabelen, tenzij je een expert bent en werkt aan een serieuze low-level code. Dat is wat ik van plan ben te doen.

Zie voor meer informatie over dit soort dingen deze blog post.


1797
2018-06-12 00:23



Ik zal alleen de analogie geven waarmee ik geheugenconsistentie modellen (of geheugenmodellen, kortweg) begrijp. Het is geïnspireerd op het baanbrekende artikel van Leslie Lamport "Tijd, klokken en de volgorde van gebeurtenissen in een verdeeld systeem". De analogie is passend en heeft een fundamentele betekenis, maar kan voor veel mensen een overkill zijn. Ik hoop echter dat het een mentaal beeld (een picturale weergave) oplevert dat het denken over geheugenconsistentiemodellen vergemakkelijkt.

Laten we de geschiedenis van alle geheugenlocaties bekijken in een ruimte-tijddiagram waarin de horizontale as de adresruimte vertegenwoordigt (dwz elke geheugenlocatie wordt vertegenwoordigd door een punt op die as) en de verticale as de tijd weergeeft (we zullen zien dat, in het algemeen is er geen universeel begrip van tijd). De geschiedenis van waarden die door elke geheugenlocatie wordt vastgehouden, wordt daarom voorgesteld door een verticale kolom op dat geheugenadres. Elke waardeverandering is het gevolg van een van de threads die een nieuwe waarde naar die locatie schrijft. Door een geheugen afbeelding, we zullen het aggregaat / de combinatie van waarden van alle geheugenlocaties waarnemen op een bepaald moment door een bepaalde thread.

Citeren van "A Primer op geheugenconsistentie en cachecoherentie"

Het intuïtieve (en meest beperkende) geheugenmodel is sequentiële consistentie (SC) waarin een uitvoering met meerdere threads eruit moet zien als een verscherving van de opeenvolgende uitvoeringen van elke samenstellende thread, alsof de threads in de tijd zijn gemultiplext op een processor met één kern.

Die globale geheugenvolgorde kan variëren van de ene run van het programma naar het andere en is misschien niet op voorhand bekend. Het karakteristieke kenmerk van SC is de reeks horizontale segmenten in het diagram van de adresruimte en tijdweergave vlakken van gelijktijdigheid (d.w.z. geheugenbeelden). Op een bepaald vlak zijn alle gebeurtenissen (of geheugenwaarden) gelijktijdig. Er is een idee van Absolute tijd, waarin alle threads overeenkomen welke geheugenwaarden gelijktijdig zijn. In SC is er altijd een momentopname van slechts één geheugenafbeelding die door alle threads wordt gedeeld. Dat wil zeggen dat op elk moment van de tijd alle processors het eens zijn over het geheugenbeeld (d.w.z. de totale inhoud van het geheugen). Dit betekent niet alleen dat alle threads dezelfde reeks waarden voor alle geheugenlocaties bekijken, maar ook dat alle processors hetzelfde observeren combinaties van waarden van alle variabelen. Dit is hetzelfde als zeggen dat alle geheugenbewerkingen (op alle geheugenlocaties) door alle threads in dezelfde totale volgorde worden waargenomen.

In ontspannen geheugenmodellen, zal elke draad op zijn eigen manier adres-ruimte-tijd versnipperen, waarbij de enige beperking is dat plakjes van elke draad elkaar niet zullen kruisen omdat alle threads overeenstemming moeten bereiken over de geschiedenis van elke individuele geheugenlocatie (natuurlijk segmenten van verschillende threads kunnen elkaar kruisen en zullen elkaar kruisen). Er is geen universele manier om het op te splitsen (geen bevoorrechte weergave van adres-ruimte-tijd). Segmenten hoeven niet vlak (of lineair) te zijn. Ze kunnen worden gebogen en dit is wat een thread leeswaarden kan laten schrijven door een andere thread uit de volgorde waarin ze zijn geschreven. Historieken van verschillende geheugenlocaties kunnen willekeurig ten opzichte van elkaar verschuiven (of uitgerekt worden) wanneer bekeken door een bepaalde thread. Elke thread zal een ander gevoel hebben van welke gebeurtenissen (of, equivalent, geheugenwaarden) gelijktijdig zijn. De verzameling gebeurtenissen (of geheugenwaarden) die gelijktijdig zijn met een thread, is niet gelijktijdig met een andere. Dus, in een ontspannen geheugenmodel, observeren alle threads nog steeds dezelfde geschiedenis (d.w.z. reeks waarden) voor elke geheugenlocatie. Maar ze kunnen verschillende geheugenbeelden waarnemen (d.w.z. combinaties van waarden van alle geheugenlocaties). Zelfs als twee verschillende geheugenlocaties opeenvolgend door dezelfde thread worden geschreven, kunnen de twee nieuw geschreven waarden in andere volgorde door andere threads worden waargenomen.

[Afbeelding uit Wikipedia] Picture from Wikipedia

Lezers bekend met Einstein's Speciale relativiteitstheorie zal opmerken waar ik op zinspeel. Minkowski's woorden vertalen naar het rijk van geheugenmodellen: adresruimte en tijd zijn schaduwen van adresruimte-tijd. In dit geval projecteert elke waarnemer (dus draad) schaduwen van gebeurtenissen (dwz geheugenopslag / belasting) op zijn eigen wereldlijn (dwz zijn tijdas) en zijn eigen vlak van gelijktijdigheid (zijn adres-ruimteas). . Threads in het C ++ 11-geheugenmodel komen overeen waarnemers die in relatieve relativiteit ten opzichte van elkaar bewegen. Sequentiële consistentie komt overeen met de Galilese ruimte-tijd (d.w.z. alle waarnemers zijn het eens over één absolute volgorde van gebeurtenissen en een algemeen gevoel van gelijktijdigheid).

De overeenkomst tussen geheugenmodellen en speciale relativiteit komt voort uit het feit dat beide een gedeeltelijk geordende reeks gebeurtenissen definiëren, vaak een causale reeks genoemd. Sommige gebeurtenissen (d.w.z. geheugenopslag) kunnen invloed hebben op (maar worden niet beïnvloed door) andere gebeurtenissen. Een C ++ 11-thread (of waarnemer in de natuurkunde) is niet meer dan een ketting (d.w.z. een volledig geordende reeks) van gebeurtenissen (bijv. Geheugen wordt geladen en opslaat naar mogelijk verschillende adressen).

In relativiteit wordt een bepaalde orde hersteld in het ogenschijnlijk chaotische beeld van gedeeltelijk geordende gebeurtenissen, aangezien de enige temporele ordening waar alle waarnemers het over eens zijn, de volgorde is tussen "tijdevenementen" (dwz die gebeurtenissen die in principe te verbinden zijn met elk deeltje dat langzamer gaat) dan de snelheid van het licht in een vacuüm). Alleen de timelike gerelateerde evenementen worden onveranderlijk geordend. Time in Physics, Craig Callender.

In het C ++ 11-geheugenmodel wordt een vergelijkbaar mechanisme (het consistentie-model voor de vrijgave van resultaten) gebruikt om deze vast te stellen lokale causaliteitsverhoudingen.

Om een ​​definitie van geheugenconsistentie en een motivatie voor het afschaffen van SC te geven, zal ik citeren uit "A Primer op geheugenconsistentie en cachecoherentie"

Voor een gedeelde geheugenmachine definieert het geheugenconsistentiemodel het architectonisch zichtbare gedrag van zijn geheugensysteem. Het correctheidscriterium voor een enkelvoudig core partities gedrag tussen "één correct resultaat"En"veel onjuiste alternatieven”. Dit komt omdat de architectuur van de processor vereist dat de uitvoering van een thread een gegeven ingangstoestand omzet in een enkele, goed gedefinieerde uitvoerstatus, zelfs op een kern buiten de bestelling. Gedeelde geheugenconsistentiemodellen hebben echter betrekking op de belastingen en winkels van meerdere threads en zijn meestal toegestaan veel correcte uitvoeringen terwijl veel (meer) onjuiste fouten worden geweigerd. De mogelijkheid van meerdere correcte uitvoeringen is te wijten aan het feit dat de ISA het mogelijk maakt om meerdere threads gelijktijdig uit te voeren, vaak met vele mogelijke juridische verstrengeling van instructies van verschillende threads.

Ontspannen of zwak Geheugenconsistentie-modellen worden gemotiveerd door het feit dat de meeste geheugenvolgorden in sterke modellen niet nodig zijn. Als een thread tien gegevensitems en vervolgens een synchronisatievlag bijwerkt, maakt programmeurs het meestal niet uit als de gegevensitems in volgorde ten opzichte van elkaar worden bijgewerkt, maar alleen dat alle gegevensitems worden bijgewerkt voordat de markering wordt bijgewerkt (meestal geïmplementeerd met behulp van FENCE-instructies) ). Ontspannen modellen trachten deze verhoogde flexibiliteit van bestellingen te vangen en alleen de orders te behouden die programmeurs "vereisen"Om zowel hogere prestaties als correctheid van SC te krijgen. In bepaalde architecturen worden bijvoorbeeld FIFO-schrijfbuffers gebruikt door elke kern om de resultaten van toegewijde (gepensioneerde) winkels vast te houden voordat de resultaten naar de caches worden geschreven. Deze optimalisatie verbetert de prestaties, maar schendt SC. De schrijfbuffer verbergt de latentie van het repareren van een misser in de winkel. Omdat winkels gebruikelijk zijn, is het een belangrijk voordeel om te voorkomen dat ze op de meeste van hen blijven hangen. Voor een single-coreprocessor kan een schrijfbuffer architectonisch onzichtbaar worden gemaakt door ervoor te zorgen dat een belasting naar adres A de waarde van de meest recente store retourneert naar A, zelfs als een of meer winkels naar A zich in de schrijfbuffer bevinden. Dit wordt meestal gedaan door de waarde van de meest recente opslag naar A te omzeilen van A, waarbij "meest recente" wordt bepaald door de programmavolgorde, of door een lading A te blokkeren als een winkel naar A zich in de schrijfbuffer bevindt . Als er meerdere cores worden gebruikt, heeft elke zijn eigen overbruggende schrijfbuffer. Zonder schrijfbuffers is de hardware SC, maar met schrijfbuffers, dat is het niet, waardoor de schrijfbuffers architecturaal zichtbaar zijn in een multicore-processor.

Store-store herschikken kan gebeuren als een kern een niet-FIFO-schrijfbuffer heeft die winkels laat vertrekken in een andere volgorde dan de volgorde waarin ze zijn ingevoerd. Dit kan gebeuren als de eerste store in de cache mist tijdens de tweede hits of als de tweede store kan samenvallen met een eerdere store (d.w.z. vóór de eerste store). Load-load herschikking kan ook plaatsvinden op dynamisch geplande cores die instructies uitvoeren buiten de programmeervolgorde. Dat kan hetzelfde gedrag vertonen als het herschikken van winkels op een andere kern (kan je een voorbeeld van een interleaving tussen twee threads bedenken?). Het opnieuw ordenen van een eerdere belasting met een latere opslag (een hersortering van de laadruimte) kan veel onjuist gedrag veroorzaken, zoals het laden van een waarde na het ontgrendelen van de vergrendeling die deze beschermt (als de opslag de ontgrendelingsbewerking is). Houd er rekening mee dat herladingen in de winkel opnieuw kunnen optreden vanwege lokale bypass in de algemeen geïmplementeerde FIFO-schrijfbuffer, zelfs met een kern die alle instructies in de programmavolgorde uitvoert.

Omdat de samenhang van de cache en de consistentie van het geheugen soms worden verward, is het leerzaam om ook dit citaat te hebben:

In tegenstelling tot consistentie, cache-coherentie is niet zichtbaar voor software noch vereist. Coherence probeert de caches van een gedeeld geheugen systeem functioneel onzichtbaar te maken als de caches in een single-core systeem. Correcte coherentie zorgt ervoor dat een programmeur niet kan bepalen of en waar een systeem caches heeft door de resultaten van belastingen en winkels te analyseren. Dit komt omdat de juiste samenhang ervoor zorgt dat de caches nooit nieuw of anders mogelijk maken functioneel gedrag (programmeurs kunnen nog steeds waarschijnlijke cache-structuur met behulp van timing informatie). Het belangrijkste doel van cache-coherentieprotocollen is het behouden van de single-writer-multiple-readers (SWMR) invariant voor elke geheugenlocatie.   Een belangrijk onderscheid tussen coherentie en consistentie is dat coherentie wordt gespecificeerd op a per geheugenlocatie basis, terwijl consistentie wordt gespecificeerd ten opzichte van alle geheugen locaties.

Voortzetting van ons mentale beeld, de SWMR-invariant correspondeert met de fysieke vereiste dat er hoogstens één deeltje zich op één locatie bevindt, maar er kan een onbeperkt aantal waarnemers van elke locatie zijn.


279
2017-08-29 20:42



Dit is nu een meerjarige vraag, maar omdat het erg populair is, is het de moeite waard om een ​​fantastisch hulpmiddel te noemen om meer te weten te komen over het C ++ 11-geheugenmodel. Ik zie geen zin in het samenvatten van zijn toespraak om dit nog een volledig antwoord te geven, maar aangezien dit de man is die de standaard heeft geschreven, denk ik dat het de moeite waard is om naar de toespraak te kijken.

Herb Sutter praat drie uur lang over het C ++ 11-geheugenmodel met de naam "atomic <> Weapons", beschikbaar op de Channel9-site - deel 1 en deel 2. Het gesprek is behoorlijk technisch en behandelt de volgende onderwerpen:

  1. Optimalisaties, Races en het geheugenmodel
  2. Bestellen - Wat: verwerven en vrijgeven
  3. Bestellen - Hoe: mutsen, atomen en / of omheiningen
  4. Andere beperkingen op compilers en hardware
  5. Code Gen & Performance: x86 / x64, IA64, POWER, ARM
  6. Relaxed Atomics

Het gesprek gaat niet verder in op de API, maar veeleer op de redenering, achtergrond, onder de motorkap en achter de schermen (wist u dat ontspannen semantiek aan de standaard werd toegevoegd alleen omdat POWER en ARM gesynchroniseerde belasting niet efficiënt ondersteunen?).


79
2017-12-20 13:22



Dit betekent dat de standaard nu multi-threading definieert, en het definieert wat er gebeurt in de context van meerdere threads. Natuurlijk hebben mensen verschillende implementaties gebruikt, maar dat is hetzelfde als vragen waarom we een moeten hebben std::string wanneer we allemaal een zelfgemaakte rol konden gebruiken string klasse.

Wanneer je het hebt over POSIX-threads of Windows-threads, dan is dit een beetje een illusie, want eigenlijk heb je het over x86-threads, omdat het een hardwarefunctie is die tegelijkertijd wordt uitgevoerd. Het C ++ 0x-geheugenmodel biedt garanties, of u nu op x86 of ARM werkt, of MIPS, of iets anders dat je kunt bedenken.


67
2018-06-11 23:42



Voor talen die geen geheugenmodel specificeren, schrijft u code voor de taal en het geheugenmodel gespecificeerd door de processorarchitectuur. De processor kan ervoor kiezen om geheugentoegang opnieuw in te stellen voor prestaties. Zo, als je programma dataraces heeft (een data race is wanneer het mogelijk is dat meerdere kernen / hyper-threads gelijktijdig toegang hebben tot hetzelfde geheugen), dan is je programma niet cross-platform vanwege zijn afhankelijkheid van het geheugenmodel van de processor. U kunt de handleidingen van de Intel- of AMD-software raadplegen om erachter te komen hoe de processors de geheugentoegang opnieuw kunnen bestellen.

Heel belangrijk is dat sloten (en semantiek op basis van gelijktijdigheid met vergrendeling) doorgaans op een platformoverschrijdende manier worden geïmplementeerd ... Dus als u standaardvergrendelingen gebruikt in een multithreaded programma zonder dataraces, dan geen zorgen te maken over cross-platform geheugenmodellen.

Interessant is dat Microsoft-compilers voor C ++ semantiek van verwerving / publicatie hebben voor vluchtige inhoud, wat een C ++ -extensie is om het gebrek aan een geheugenmodel in C ++ aan te pakken http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs.80).aspx. Aangezien Windows alleen op x86 / x64 draait, zegt dat echter niet veel (Intel- en AMD-geheugenmodellen maken het eenvoudig en efficiënt om semantiek voor verwerving / publicatie in een taal te implementeren).


49
2017-07-26 04:27



Als je mutexen gebruikt om al je gegevens te beschermen, hoef je je echt geen zorgen te maken. Mutexes hebben altijd voldoende bestel- en zichtgaranties gegeven.

Als u nu atomaire of lock-free algoritmen gebruikt, moet u nadenken over het geheugenmodel. Het geheugenmodel beschrijft precies wanneer atomica bestel- en zichtgaranties biedt en biedt draagbare hekken voor handgecodeerde garanties.

Voorheen werd atomisatie gedaan met behulp van de compiler-intrinsiek of een bibliotheek op een hoger niveau. Hekken zouden zijn uitgevoerd met behulp van CPU-specifieke instructies (geheugenbarrières).


22
2018-06-11 23:49