Frage C ++ 11 führte ein standardisiertes Speichermodell ein. Was heißt das? Und wie wird es die C ++ Programmierung beeinflussen?


C ++ 11 führte ein standardisiertes Speichermodell ein, aber was genau bedeutet das? Und wie wird es die C ++ Programmierung beeinflussen?

Dieser Artikel (durch Gavin Clarke wer zitiert Herb Sutter) sagt, dass,

Das Speichermodell bedeutet C ++ - Code   hat jetzt eine standardisierte Bibliothek zum Anrufen   unabhängig davon, wer den Compiler gemacht hat   und auf welcher Plattform es läuft.   Es gibt eine Standardmethode, um zu steuern, wie   verschiedene Threads sprechen mit dem   Prozessorspeicher.

"Wenn du über das Teilen sprecht   [code] über verschiedene Kerne, das ist   Im Standard sprechen wir darüber   das Speichermodell. Wir gehen zu   optimieren Sie es, ohne das zu brechen   folgende Annahmen gehen Menschen   den Code machen " Sutter sagte.

Also ich kann sich einprägen Diese und ähnliche Absätze sind online verfügbar (da ich seit meiner Geburt mein eigenes Gedächtnismodell habe: P) und kann sogar als Antwort auf Fragen von anderen posten, aber um ehrlich zu sein, verstehe ich das nicht genau.

Was ich im Grunde wissen möchte, ist, dass C ++ - Programmierer früher schon Multithread-Anwendungen entwickelt haben. Wie kommt es dann darauf an, ob es sich um POSIX-Threads oder Windows-Threads oder C ++ 11-Threads handelt? Was sind die Vorteile? Ich möchte die Details auf niedriger Ebene verstehen.

Ich habe auch das Gefühl, dass das C ++ 11-Speichermodell irgendwie mit C ++ 11-Multithreading-Unterstützung verwandt ist, da ich diese beiden oft zusammen sehe. Wenn ja, wie genau? Warum sollten sie verwandt sein?

Da ich nicht weiß, wie das Internal von Multi-Threading funktioniert und was Memory Model im Allgemeinen bedeutet, bitte helfen Sie mir, diese Konzepte zu verstehen. :-)


1550
2018-06-11 23:30


Ursprung


Antworten:


Zuerst müssen Sie lernen, wie ein Sprachanwalt zu denken.

Die C ++ - Spezifikation bezieht sich nicht auf einen bestimmten Compiler, ein bestimmtes Betriebssystem oder eine bestimmte CPU. Es bezieht sich auf ein abstrakte Maschine Das ist eine Verallgemeinerung der tatsächlichen Systeme. In der Welt der Sprachjuristen besteht die Aufgabe des Programmierers darin, Code für die abstrakte Maschine zu schreiben; Die Aufgabe des Compilers ist es, diesen Code auf einer konkreten Maschine zu aktualisieren. Wenn Sie streng nach der Spezifikation codieren, können Sie sicher sein, dass Ihr Code kompiliert und ohne Änderung auf jedem System mit einem kompatiblen C ++ - Compiler ausgeführt wird, egal ob heute oder in 50 Jahren.

Die abstrakte Maschine in der Spezifikation C ++ 98 / C ++ 03 ist grundsätzlich single-threaded. Daher ist es nicht möglich, Multithread-C ++ - Code zu schreiben, der in Bezug auf die Spezifikation "vollständig portierbar" ist. Die Spezifikation sagt noch nichts über die Atomarität von Speicher lädt und speichert oder die Auftrag in denen Ladungen und Ladungen passieren können, egal wie Mutexe.

Natürlich können Sie in der Praxis Multi-Thread-Code für bestimmte konkrete Systeme schreiben - wie Pthreads oder Windows. Aber es gibt keinen StandardMöglichkeit, Multi-Thread-Code für C ++ 98 / C ++ 03 zu schreiben.

Die abstrakte Maschine in C ++ 11 ist vom Entwurf her multi-threaded. Es hat auch eine gut definierte Speichermodell; Das heißt, was der Compiler tun kann und was nicht, wenn es darum geht, auf Speicher zuzugreifen.

Betrachten Sie das folgende Beispiel, in dem auf ein Paar globaler Variablen gleichzeitig von zwei Threads zugegriffen wird:

           Global
           int x, y;

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

Was könnte Thread 2 ausgeben?

Unter C ++ 98 / C ++ 03 ist dies nicht einmal Undefined Behavior; die Frage selbst ist bedeutungslos weil der Standard nichts als "Thread" bezeichnet.

Unter C ++ 11 ist das Ergebnis Undefined Behavior, weil Lasten und Speicher im Allgemeinen nicht atomar sein müssen. Was nicht wie eine Verbesserung erscheinen mag ... Und das ist es nicht.

Aber mit C ++ 11 können Sie dies schreiben:

           Global
           atomic<int> x, y;

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

Jetzt werden die Dinge viel interessanter. Vor allem ist das Verhalten hier definiert. Thread 2 könnte jetzt drucken 0 0 (wenn es vor Thread 1 läuft), 37 17 (wenn es nach Thread 1 läuft), oder 0 17 (wenn es nach dem Zuordnen von Thread 1 zu x aber vor dem Zuordnen zu y ausgeführt wird).

Was es nicht drucken kann ist 37 0, weil der Standardmodus für atomare Ladungen / Speicher in C ++ 11 erzwungen werden soll sequentielle Konsistenz. Das bedeutet nur, dass alle Ladevorgänge und Speichervorgänge "so als ob" sie in der Reihenfolge geschehen würden, in der Sie sie in jedem Thread geschrieben haben, während Operationen zwischen Threads verschachtelt werden können, wie es dem System gefällt. Das Standardverhalten von Atomics bietet beides Atomarität und Bestellung für Ladungen und Geschäfte.

Auf einer modernen CPU kann die Sicherstellung der sequentiellen Konsistenz teuer sein. Insbesondere wird der Compiler wahrscheinlich zwischen jedem Zugriff volle Speicherbarrieren aussenden. Aber wenn Ihr Algorithmus Out-Of-Order-Lasten und -Speicher tolerieren kann; wenn es Atomizität erfordert, aber keine Ordnung; d.h. wenn es tolerieren kann 37 0 als Ausgabe von diesem Programm, dann können Sie dies schreiben:

           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;

Je moderner die CPU ist, desto wahrscheinlicher ist es, dass sie schneller ist als das vorherige Beispiel.

Schließlich, wenn Sie nur bestimmte Lasten und Speicher in der richtigen Reihenfolge halten müssen, können Sie schreiben:

           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;

Dies bringt uns zurück zu den bestellten Ladungen und speichert - so 37 0 ist keine mögliche Ausgabe mehr - aber mit minimalem Overhead. (In diesem trivialen Beispiel ist das Ergebnis dasselbe wie die vollständige sequenzielle Konsistenz; in einem größeren Programm wäre dies nicht der Fall.)

Natürlich, wenn die einzigen Ausgaben, die Sie sehen möchten, sind 0 0 oder 37 17, können Sie einfach einen Mutex um den ursprünglichen Code wickeln. Aber wenn du so weit gelesen hast, wette ich, du weißt bereits, wie das funktioniert, und diese Antwort ist schon länger, als ich es beabsichtigt hatte :-).

Also, unter dem Strich. Mutexe sind großartig und C ++ 11 standardisiert sie. Aber manchmal wollen Sie aus Gründen der Leistungsfähigkeit niedrigere Grundelemente (z. B. den Klassiker) doppeltes Sperrmuster). Der neue Standard bietet High-Level-Gadgets wie Mutexe und Zustandsvariablen und bietet auch Low-Level-Gadgets wie atomare Typen und die verschiedenen Geschmacksrichtungen der Speicherbarriere. Jetzt können Sie anspruchsvolle, hochperformante simultane Routinen vollständig in der vom Standard spezifizierten Sprache schreiben, und Sie können sicher sein, dass Ihr Code sowohl auf den heutigen als auch den heutigen Systemen kompiliert und unverändert bleibt.

Obwohl man ehrlich ist, sollte man sich, wenn man kein Experte ist und an einem ernsthaften Low-Level-Code arbeitet, wahrscheinlich an Mutexe und Condition-Variablen halten. Das beabsichtige ich zu tun.

Für mehr zu diesem Thema, siehe dieser Blogbeitrag.


1797
2018-06-12 00:23



Ich werde nur die Analogie geben, mit der ich Speicherkonsistenzmodelle (oder kurz Speichermodelle) verstehe. Es ist inspiriert von Leslie Lamports bahnbrechendem Papier "Zeit, Uhren und die Reihenfolge der Ereignisse in einem verteilten System". Die Analogie ist zutreffend und hat grundlegende Bedeutung, aber kann für viele Leute übertrieben sein. Ich hoffe jedoch, dass es ein mentales Bild (eine bildliche Darstellung) liefert, das das Nachdenken über Gedächtniskonsistenzmodelle erleichtert.

Betrachten wir die Historien aller Speicherorte in einem Raum-Zeit-Diagramm, in dem die horizontale Achse den Adressraum repräsentiert (dh jeder Speicherplatz wird durch einen Punkt auf dieser Achse repräsentiert) und die vertikale Achse die Zeit darstellt (wir werden sehen, im Allgemeinen gibt es keine universelle Vorstellung von Zeit). Die Historie der Werte, die von jeder Speicherstelle gehalten werden, wird daher durch eine vertikale Spalte bei dieser Speicheradresse dargestellt. Jede Wertänderung ist darauf zurückzuführen, dass einer der Threads einen neuen Wert an diesen Ort schreibt. Durch eine SpeicherbildWir werden die Gesamtheit / Kombination von Werten aller Speicherorte, die beobachtbar sind, bezeichnen zu einer bestimmten Zeit durch ein bestimmter Thread.

Zitat von "Eine Grundlegendes zur Speicherkonsistenz und Cache-Kohärenz"

Das intuitive (und restriktivste) Speichermodell ist die sequentielle Konsistenz (SC), in der eine Multithread-Ausführung wie eine Verschachtelung der sequentiellen Ausführungen jedes konstituierenden Threads aussehen soll, als ob die Threads auf einem Single-Core-Prozessor zeitgemultiplext wären.

Diese globale Speicherordnung kann von einem Programmlauf zum anderen variieren und ist möglicherweise nicht vorher bekannt. Das charakteristische Merkmal von SC ist der Satz von horizontalen Schichten in dem Adressraum-Zeit-Diagramm, das darstellt Ebenen der Gleichzeitigkeit (d. h. Speicherbilder). Auf einer gegebenen Ebene sind alle seine Ereignisse (oder Speicherwerte) gleichzeitig. Es gibt eine Vorstellung von Absolute Zeit, in dem alle Threads übereinstimmen, welche Speicherwerte gleichzeitig sind. In SC gibt es zu jedem Zeitpunkt nur ein Speicherbild, das von allen Threads geteilt wird. Das heißt, zu jedem Zeitpunkt vereinbaren alle Prozessoren das Speicherbild (d. H. Den Gesamtspeicherinhalt). Das bedeutet nicht nur, dass alle Threads für alle Speicherorte die gleiche Sequenz von Werten anzeigen, sondern auch, dass alle Prozessoren dasselbe beobachten Kombinationen von Werten aller Variablen. Dies ist gleichbedeutend damit, dass alle Speicheroperationen (an allen Speicherorten) in derselben Reihenfolge von allen Threads beobachtet werden.

In entspannten Speichermodellen schneidet jeder Thread die Adressraum-Zeit auf seine eigene Weise auf. Die einzige Einschränkung besteht darin, dass sich die Segmente eines Threads nicht kreuzen dürfen, da alle Threads sich auf den Verlauf jedes einzelnen Speicherplatzes einigen müssen (natürlich , Scheiben verschiedener Fäden können und werden einander kreuzen). Es gibt keine universelle Möglichkeit, sie aufzuteilen (keine privilegierte Foliation von Adresse-Raum-Zeit). Slices müssen nicht planar (oder linear) sein. Sie können gekrümmt sein, und dies kann dazu führen, dass ein Thread Werte liest, die von einem anderen Thread außerhalb der Reihenfolge geschrieben wurden, in der sie geschrieben wurden. Historien verschiedener Speicherpositionen können beliebig zueinander verschoben (oder gestreckt) werden wenn es von einem bestimmten Thread angezeigt wird. Jeder Thread hat einen unterschiedlichen Sinn dafür, welche Ereignisse (oder äquivalent Speicherwerte) gleichzeitig sind. Die Gruppe von Ereignissen (oder Speicherwerten), die gleichzeitig mit einem Thread ausgeführt werden, ist nicht gleichzeitig mit einem anderen. Somit beobachten in einem entspannten Speichermodell alle Threads immer noch den gleichen Verlauf (d. H. Die Folge von Werten) für jeden Speicherort. Sie können jedoch unterschiedliche Speicherbilder (d. H. Kombinationen von Werten aller Speicherstellen) beobachten. Selbst wenn zwei verschiedene Speicherplätze nacheinander von demselben Thread geschrieben werden, können die zwei neu geschriebenen Werte in anderer Reihenfolge von anderen Threads beobachtet werden.

[Bild aus Wikipedia] Picture from Wikipedia

Leser, die mit Einsteins vertraut sind Spezielle Relativitätstheorie werde bemerken, worauf ich anspiele. Die Wörter von Minkowski in das Reich der Speichermodelle übersetzen: Adressraum und Zeit sind Schatten von Adresse-Raum-Zeit. In diesem Fall projiziert jeder Beobachter (dh ein Faden) Schatten von Ereignissen (dh Speicher speichert / lädt) auf seine eigene Weltlinie (dh seine Zeitachse) und seine eigene Ebene der Gleichzeitigkeit (seine Adresse-Raum-Achse). . Threads im C ++ 11-Speichermodell entsprechen Beobachter die sich in der speziellen Relativität relativ zueinander bewegen. Sequentielle Konsistenz entspricht dem Galiläische Raumzeit (d. h. alle Beobachter sind sich einig über eine absolute Reihenfolge der Ereignisse und ein globales Gefühl der Gleichzeitigkeit).

Die Ähnlichkeit zwischen Speichermodellen und spezieller Relativitätstheorie rührt von der Tatsache her, dass beide eine teilweise geordnete Menge von Ereignissen definieren, die oft als kausale Menge bezeichnet werden. Einige Ereignisse (d. H. Speichervorräte) können andere Ereignisse beeinflussen (aber nicht davon betroffen sein). Ein C ++ 11-Thread (oder ein Beobachter in der Physik) ist nicht mehr als eine Kette (d. H. Eine vollständig geordnete Menge) von Ereignissen (z. B. lädt und speichert Speicher auf möglicherweise unterschiedliche Adressen).

In der Relativitätstheorie wird eine Ordnung in das scheinbar chaotische Bild von teilweise geordneten Ereignissen wiederhergestellt, da die einzige zeitliche Ordnung, auf die sich alle Beobachter geeinigt haben, die Ordnung unter "zeitartigen" Ereignissen ist (dh jene Ereignisse, die im Prinzip durch jedes langsamere Teilchen verbindbar sind) als die Lichtgeschwindigkeit im Vakuum). Nur die zeitähnlichen Ereignisse sind invariant geordnet. Zeit in der Physik, Craig Callender.

Im C ++ 11-Speichermodell wird ein ähnlicher Mechanismus (das Acquire-Release-Konsistenzmodell) verwendet, um diese zu ermitteln lokale Kausalitätsbeziehungen.

Um eine Definition der Speicherkonsistenz und eine Motivation für den Verzicht auf SC zu geben, werde ich aus zitieren "Eine Grundlegendes zur Speicherkonsistenz und Cache-Kohärenz"

Bei einer Maschine mit gemeinsam genutztem Speicher definiert das Speicherkonsistenzmodell das architektonisch sichtbare Verhalten seines Speichersystems. Das Korrektheitskriterium für einen einzelnen Prozessorkern teilt das Verhalten zwischen "Ein richtiges Ergebnis" und "viele falsche Alternativen". Dies liegt daran, dass die Architektur des Prozessors vorschreibt, dass die Ausführung eines Threads einen gegebenen Eingabezustand in einen einzigen wohldefinierten Ausgabezustand umwandelt, selbst bei einem Out-of-Order-Core. Shared-Memory-Consistency-Modelle betreffen jedoch die Lasten und Speicher von mehreren Threads und ermöglichen dies normalerweise viele korrekte Ausführungen während viele (mehr) falsche verboten werden. Die Möglichkeit mehrerer korrekter Ausführungen ist darauf zurückzuführen, dass die ISA die gleichzeitige Ausführung mehrerer Threads erlaubt, oft mit vielen möglichen legalen Interleavings von Anweisungen von verschiedenen Threads.

Entspannt oder schwach Speicherkonsistenzmodelle werden durch die Tatsache motiviert, dass die meisten Speicherordnungen in starken Modellen nicht notwendig sind. Wenn ein Thread zehn Datenelemente aktualisiert und dann ein Synchronisierungskennzeichen, ist es Programmierern normalerweise egal, ob die Datenelemente in der Reihenfolge zueinander aktualisiert werden, sondern nur, dass alle Datenelemente aktualisiert werden, bevor das Flag aktualisiert wird (normalerweise unter Verwendung von FENCE-Befehlen implementiert) ). Entspannte Modelle versuchen, diese erhöhte Bestellflexibilität zu erfassen und nur die Aufträge zu behalten, die Programmierer "benötigen"Um sowohl höhere Leistung als auch Korrektheit von SC zu erzielen. Zum Beispiel werden in bestimmten Architekturen FIFO-Schreibpuffer von jedem Kern verwendet, um die Ergebnisse von festgeschriebenen (zurückgezogenen) Speichern zu halten, bevor die Ergebnisse in die Caches geschrieben werden. Diese Optimierung verbessert die Leistung, verletzt jedoch SC. Der Schreibpuffer verbirgt die Latenz des Wartens eines Speicherfehlers. Da Geschäfte üblich sind, ist es ein wichtiger Vorteil, bei den meisten von ihnen das Abwürgen zu vermeiden. Bei einem Single-Core-Prozessor kann ein Schreibpuffer architektonisch unsichtbar gemacht werden, indem sichergestellt wird, dass ein Ladevorgang an Adresse A den Wert des letzten Speichers an A zurückgibt, auch wenn sich ein oder mehrere Speicher für A im Schreibpuffer befinden. Dies geschieht typischerweise, indem entweder der Wert des letzten Speichers an A zu dem Ladevorgang von A umgangen wird, wobei "zuletzt" durch die Programmreihenfolge bestimmt wird, oder indem ein Ladevorgang von A angehalten wird, wenn ein Speicher zu A im Schreibpuffer ist . Wenn mehrere Kerne verwendet werden, hat jeder seinen eigenen umgehenden Schreibpuffer. Ohne Schreibpuffer ist die Hardware SC, aber mit Schreibpuffern ist dies nicht der Fall, wodurch Schreibpuffer in einem Multicore-Prozessor architektonisch sichtbar gemacht werden.

Store-Store-Neuordnung kann passieren, wenn ein Kern einen Nicht-FIFO-Schreibpuffer hat, der Speicher in einer anderen Reihenfolge ablaufen lässt als in der Reihenfolge, in der sie eingegeben wurden. Dies kann auftreten, wenn der erste Speicher im Cache fehlschlägt, während der zweite auftritt, oder wenn der zweite Speicher mit einem früheren Speicher koaleszieren kann (d. H. Vor dem ersten Speicher). Die Neuordnung von Ladeanforderungen kann auch bei dynamisch geplanten Kernen auftreten, die Anweisungen außerhalb der Programmreihenfolge ausführen. Das kann sich genauso verhalten wie das Umsortieren von Speichern auf einem anderen Kern (Können Sie eine Beispielverschachtelung zwischen zwei Threads finden?). Das Neuanordnen einer früheren Ladung mit einem späteren Speicher (eine Neuordnung des Lade-Speichers) kann viele falsche Verhaltensweisen verursachen, z. B. das Laden eines Werts nach dem Aufheben der Sperre, die ihn schützt (wenn der Speicher die Entsperr-Operation ist). Es ist zu beachten, dass Speicherlade-Umordnungen auch aufgrund einer lokalen Umgehung in dem gemeinsam implementierten FIFO-Schreibpuffer auftreten können, selbst mit einem Kern, der alle Befehle in der Programmreihenfolge ausführt.

Weil Cache-Kohärenz und Speicherkonsistenz manchmal verwirrt sind, ist es lehrreich, auch dieses Zitat zu haben:

Im Gegensatz zur Konsistenz, Cache-Kohärenz ist weder für Software sichtbar noch erforderlich. Coherence versucht, die Caches eines Shared-Memory-Systems so funktionell unsichtbar zu machen wie die Caches eines Single-Core-Systems. Korrekte Kohärenz stellt sicher, dass ein Programmierer nicht feststellen kann, ob und wo ein System Caches hat, indem er die Ergebnisse von Ladevorgängen und Speichern analysiert. Dies liegt daran, dass eine korrekte Kohärenz sicherstellt, dass die Caches niemals neue oder andere ermöglichen funktional Verhalten (Programmierer können möglicherweise immer noch wahrscheinlich Cachestruktur mit zeitliche Koordinierung Information). Der Hauptzweck von Cache-Kohärenzprotokollen besteht darin, die Single-Writer-Multiple-Reader (SWMR) für jeden Speicherplatz invariant zu halten.   Ein wichtiger Unterschied zwischen Kohärenz und Konsistenz besteht darin, dass die Kohärenz auf a spezifiziert ist pro Speicherbasis, während die Konsistenz in Bezug auf alle Speicherorte.

In Fortsetzung unseres mentalen Bildes entspricht die SWMR-Invariante der physikalischen Anforderung, dass es höchstens ein Teilchen an einem Ort gibt, aber es kann eine unbegrenzte Anzahl von Beobachtern von jedem Ort geben.


279
2017-08-29 20:42



Dies ist jetzt eine mehrjährige Frage, aber da sie sehr beliebt ist, ist es eine erwähnenswerte Quelle wert, um etwas über das C ++ 11-Speichermodell zu lernen. Ich sehe keinen Sinn darin, seine Rede zusammenzufassen, um noch eine weitere vollständige Antwort zu geben, aber da dies der Typ ist, der den Standard tatsächlich geschrieben hat, denke ich, dass es sich lohnt, die Rede zu verfolgen.

Herb Sutter spricht drei Stunden lang über das C ++ 11-Speichermodell "atomic <> Weapons", das auf der Channel9-Website erhältlich ist - Teil 1 und Teil 2. Das Gespräch ist ziemlich technisch und umfasst folgende Themen:

  1. Optimierungen, Races und das Speichermodell
  2. Bestellung - Was: Erwerben und Freigeben
  3. Bestellung - Wie: Mutexe, Atomics und / oder Zäune
  4. Andere Einschränkungen für Compiler und Hardware
  5. Code Gen & Leistung: x86 / x64, IA64, POWER, ARM
  6. Entspannte Atomics

Der Vortrag geht nicht auf die API ein, sondern auf die Argumentation, den Hintergrund, unter der Haube und hinter den Kulissen (Wussten Sie, dass die entspannte Semantik dem Standard nur hinzugefügt wurde, weil POWER und ARM die synchronisierte Last nicht effizient unterstützen?).


79
2017-12-20 13:22



Das bedeutet, dass der Standard jetzt Multithreading definiert und definiert, was im Kontext mehrerer Threads geschieht. Natürlich verwendeten die Leute unterschiedliche Implementierungen, aber das ist wie die Frage, warum wir eine haben sollten std::string wenn wir alle ein Heimrollen benutzen könnten string Klasse.

Wenn Sie über POSIX-Threads oder Windows-Threads sprechen, ist das eine Illusion, da Sie eigentlich von x86-Threads sprechen, da es sich um eine Hardwarefunktion handelt, die gleichzeitig ausgeführt wird. Das C ++ 0x Speichermodell garantiert, ob Sie auf x86, oder ARM, oder sind MIPSoder irgendetwas anderes, was dir einfällt.


67
2018-06-11 23:42



Für Sprachen, die kein Speichermodell angeben, schreiben Sie Code für die Sprache und das von der Prozessorarchitektur angegebene Speichermodell. Der Prozessor kann wählen, Speicherzugriffe für die Leistung neu zu ordnen. Damit, wenn Ihr Programm Datenrennen hat (Ein Datenrennen ist dann möglich, wenn mehrere Kerne / Hyper-Threads gleichzeitig auf denselben Speicher zugreifen können). Ihr Programm ist dann nicht plattformübergreifend, da es vom Prozessorspeichermodell abhängig ist. In den Intel- oder AMD-Softwarehandbüchern finden Sie Informationen dazu, wie die Prozessoren Speicherzugriffe neu ordnen können.

Sehr wichtig ist, dass Sperren (und Parallelitätssemantiken mit Sperren) normalerweise plattformübergreifend implementiert werden. Wenn Sie also Standardsperren in einem Multithread-Programm ohne Datenrennen verwenden, dann Sie Sie müssen sich keine Gedanken über plattformübergreifende Speichermodelle machen.

Interessanterweise haben Microsoft-Compiler für C ++ eine Semantik für flüchtige Ausdrücke, die eine C ++ - Erweiterung ist, um mit dem Fehlen eines Speichermodells in C ++ fertig zu werden http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs.80).aspx. Da Windows jedoch nur auf x86 / x64 ausgeführt wird, sagt das nicht viel aus (Intel- und AMD-Speichermodelle machen es einfach und effizient, eine Semantik zum Akquirieren / Freigeben in einer Sprache zu implementieren).


49
2017-07-26 04:27



Wenn Sie Mutexe verwenden, um alle Ihre Daten zu schützen, sollten Sie sich keine Sorgen machen. Mutexe bieten immer ausreichende Bestell- und Sichtbarkeitsgarantien.

Wenn Sie jetzt Atomics oder Lock-Free-Algorithmen verwenden, müssen Sie über das Speichermodell nachdenken. Das Speichermodell beschreibt genau, wann Atomics Bestell- und Sichtbarkeitsgarantien bietet und portable Zäune für handcodierte Garantien bietet.

Zuvor wurden Atomics mithilfe von Compiler-Intrinsics oder einer höheren Bibliotheksebene erstellt. Zäune wären mit CPU-spezifischen Anweisungen (Speicherbarrieren) gemacht worden.


22
2018-06-11 23:49