Boost bietet einen Sample-Verweis auf Atomic Reference Geared Pointer
Hier ist das relevante Code-Snippet und die Erklärung für die verschiedenen verwendeten Anordnungen:
%Vor%Das Erhöhen des Referenzzählers kann immer mit durchgeführt werden memory_order_relaxed: Neue Referenzen auf ein Objekt können nur gebildet werden aus einer bestehenden Referenz und eine vorhandene Referenz von einer übergeben Thread zu einem anderen muss bereits eine erforderliche Synchronisierung bereitstellen.
Es ist wichtig, jeden möglichen Zugriff auf das Objekt in einem zu erzwingen Thread (über eine vorhandene Referenz) vor dem Löschen der Objekt in einem anderen Thread. Dies wird durch eine "Freigabe" erreicht Operation nach dem Löschen einer Referenz (beliebiger Zugriff auf das Objekt durch Diese Referenz muss offensichtlich vorher geschehen), und ein "erwerben" Operation vor dem Löschen des Objekts.
Es wäre möglich, memory_order_acq_rel für den fetch_sub zu verwenden Operation, aber dies führt zu unnötigen "akquirieren" Operationen, wenn die Referenzzähler erreicht noch nicht Null und kann eine Leistung auferlegen Strafe.
Ich kann nicht verstehen, warum die Barriere memory_order_acquire
vor der Operation delete x
notwendig ist. Insbesondere, wie ist es für den Compiler / Prozessor sicher, die Speicheroperationen von delete x
vor dem fetch_sub
und den Test vom Wert von x == 1
neu zu ordnen, ohne die Semantik mit einem einzigen Thread zu verletzen?
BEARBEITEN Ich denke, meine Frage war nicht sehr klar. Hier ist eine umformulierte Version:
Liefert die Kontrollabhängigkeit zwischen dem Lesen von x ( x->refcount_.fetch_sub(1, boost::memory_order_release) == 1
) und der delete x
-Operation überhaupt eine Bestellgarantie? Ist es möglich, dass der Compiler / Prozessor die Befehle, die der delete x
-Operation vor der fetch_sub
und dem Vergleich entsprechen, neu anordnet, selbst wenn man ein single-threaded-Programm betrachtet. Es wäre wirklich hilfreich, wenn die Antwort so niedrig wie möglich wäre und ein Beispielszenario enthält, in dem die Löschoperation neu geordnet wird (ohne die Singlethread-Semantik zu beeinflussen), was zeigt, dass die Reihenfolge beibehalten werden muss.
Betrachten Sie zwei Threads, die jeweils einen Verweis auf das Objekt enthalten, die die letzten beiden Referenzen sind:
%Vor% Sie müssen sicherstellen, dass alle Änderungen, die an dem Objekt von Thread 1 in //play with x here
vorgenommen wurden, für Thread 2 sichtbar sind, wenn es delete x;
aufruft. Dazu benötigen Sie einen Acquired Fence, der zusammen mit den memory_order_release
auf den fetch_sub()
Aufrufen garantiert, dass die von Thread 1 vorgenommenen Änderungen sichtbar sind.
Von Ссылка
memory_order_acquire - Eine Ladeoperation mit dieser Speicherreihenfolge führt die Erfassungsoperation an dem betroffenen Speicherort durch: prior schreibt an anderen Speicherorten nach dem Thread , der das gemacht hat Release wird in diesem Thread sichtbar.
...
Freigabe-Erwerben der Bestellung
Wenn ein atomarer Speicher in Thread A mit std :: memory_order_release und eine atomare Ladung in Thread B aus derselben Variablen wird markiert std :: memory_order_acquire, alle Speicher schreibt (nicht atomar und entspannt Atomar), die vor dem Atomladen aus der Sicht passiert ist von Thread A, werden sichtbare Nebenwirkungen in Thread B, das heißt, einmal Die atomare Ladung ist abgeschlossen, Thread B sieht garantiert alles Thread A schrieb in den Speicher.
Die Synchronisation wird nur zwischen den freigebenden Threads eingerichtet und Erlangen derselben atomaren Variable. Andere Threads können sehen unterschiedliche Reihenfolge der Speicherzugriffe als eines oder beide der synchronisierte Threads.
Auf stark geordneten Systemen (x86, SPARC TSO, IBM Mainframe) Die Bestellung von release-aware erfolgt automatisch für die meisten Vorgänge. Für diese Synchronisation werden keine zusätzlichen CPU-Anweisungen ausgegeben Modus sind nur bestimmte Compileroptimierungen betroffen (z. B. der Compiler ist es nicht erlaubt, nicht-atomare Speicher hinter dem Atom zu bewegen Speichern-release oder führen Sie nicht-atomare Lasten früher als das atomare laden-erwerben). Auf schwach geordneten Systemen (ARM, Itanium, PowerPC) spezielle CPU-Lade- oder Memory-Fence-Anweisungen müssen verwendet werden.
Dies bedeutet, dass release es anderen Threads erlaubt, ausstehende Operationen des aktuellen Threads zu synchronisieren, während das spätere acquire alle modifizierten Änderungen von den anderen Threads abruft.
Bei stark geordneten Systemen ist dies nicht so wichtig. Ich glaube nicht, dass diese Befehle sogar Code erzeugen, da die CPU Cache-Zeilen automatisch sperrt, bevor irgendwelche Schreibvorgänge auftreten können. Der Cache ist garantiert konsistent. Aber auf wöchentlich geordneten Systemen, während atomare Operationen wohldefiniert sind, könnten Operationen an anderen Teilen des Speichers ausstehen.
Sagen wir also die Threads A und B, und beide teilen einige Daten D.
Wenn der Thread-Fence vor dem Löschen abgerufen wird, synchronisiert der aktuelle Thread alle ausstehenden Operationen von anderen Threads in seinem Adressraum. Und wenn Löschen passiert, sieht er, was A in # 1 getan hat.
Tags und Links c++ multithreading shared-memory boost atomic