2016-04-24 7 views
14

Mein Verständnis von std::memory_order_acquire und std::memory_order_release ist wie folgt:Speicher Zäune: acquire/Last und Release/store

Acquire bedeutet, dass keine Speicherzugriffe, die erscheinen nach der acquire Zaun vor dem Zaun neu geordnet werden können .

Veröffentlichung bedeutet, dass kein Speicher, der vor die Freigabe Zaun werden kann, nachdem der Zaun neu geordnet erscheinen zugreift.

Was ich nicht verstehe, ist, warum insbesondere mit der C++ 11 Atomics-Bibliothek der Acquired Fence Ladeoperationen zugeordnet ist, während der Release Fence Speicheroperationen zugeordnet ist.

Um zu klären, die C++ 11 <atomic> Bibliothek Sie Speicher Zäunen auf zwei Arten angeben kann: entweder Sie einen Zaun als zusätzliches Argument zu einer atomaren Operation angeben können, wie:

x.load(std::memory_order_acquire); 

Oder Sie verwenden können std::memory_order_relaxed und geben Sie den Zaun getrennt, wie:

x.load(std::memory_order_relaxed); 
std::atomic_thread_fence(std::memory_order_acquire); 

Was ich nicht verstehe, ist, die oben genannten Definitionen von acquire gegeben und loslassen, warum C++ 11 assoziieren speziell acquire mit laden und release mit speichern? Ja, ich habe viele der Beispiele gesehen, die zeigen, wie man ein Acquire/Load mit einem release/store zwischen Threads synchronisieren kann, aber im Allgemeinen scheint es, dass Zäune erworben werden (Speicherumordnung nach Anweisung verhindern) und freigeben Zäune (verhindern Speicher-Neuordnung vor Anweisung) ist orthogonal zu der Idee von Lasten und Speichern.

Also, warum zum Beispiel nicht den Compiler lassen Sie mich sagen:

x.store(10, std::memory_order_acquire); 

Ich weiß, ich die oben memory_order_relaxed unter Verwendung erreichen können und dann eine separate atomic_thread_fence(memory_order_acquire) Aussage, aber wieder, warum kann 't ich verwende Laden direkt mit memory_order_acquire?

Ein möglicher Anwendungsfall dafür könnte sein, wenn ich, dass einige Speicher sicherstellen will, sagen x = 10, geschieht vor eine andere Aussage, dass andere Threads möglicherweise beeinflussen ausführt.

+1

In einem typischen lock-free-Algorithmus lesen Sie eine atomare, um zu sehen, ob eine freigegebene Ressource bereit ist für den Verbrauch (bereit zu erwerben), und Sie schreiben eine atomare, um anzuzeigen, dass eine freigegebene Ressource verwendet werden kann (to die Ressource freigeben). Sie möchten nicht, dass die Lesevorgänge der freigegebenen Ressource verschoben werden, bevor das Atomwachen überprüft wird. und Sie möchten nicht, dass die Initialisierung der zu teilenden Ressource nach dem Schreiben des Atoms verschoben wird, um die Freigabe anzuzeigen. –

+0

Im Beispiel ist nur 'atomic_thread_fence (std :: memory_order_acquire)' ein echter Zaun. Siehe ** 1.10: 5 Multi-Thread-Ausführungen und Datenrennen [intro.multithread] ** im Standard, der (unter Angabe des Entwurfs n3797) lautet _ "Ein Synchronisationsvorgang ohne einen zugeordneten Speicherplatz ist ein Zaun und kann entweder ein einen Zaun, einen Freisetzungszaun oder beides, einen Acquire- und einen Release-Zaun. "_ Im Gegensatz dazu ist" x.load (std :: memory_order_acquire) "eine _atomare Operation_, die eine _acquire_ Operation an" x "ausführt, es wäre eine _synchronisation operation_, wenn der Wert einem Speicher _release_ in x entspricht. – amdn

+0

In der Einleitung beschränkt der Standard (Entwurf n3797) die Übernahmevorgänge nicht auf Lasten und gibt Vorgänge an Speicher frei. Das ist bedauerlich. Sie müssen zu Klausel ** 29.3: 1 Reihenfolge und Konsistenz [atomics.order] ** gehen, um _ "memory_order_acquire, memory_order_acq_rel und memory_order_seq_cst zu finden: eine Ladeoperation führt eine Erfassungsoperation an dem betroffenen Speicherort aus" _ und _ "memory_order_release , memory_order_acq_rel, und memory_order_seq_cst: Eine Speicheroperation führt eine Freigabeoperation an dem betroffenen Speicherort aus. _ – amdn

Antwort

10

Sagen wir, ich schreibe ein paar Daten, und dann schreibe ich einen Hinweis, dass die Daten jetzt bereit sind. Es ist zwingend erforderlich, dass kein anderer Thread, der die Anzeige sieht, dass die Daten bereit sind, das Schreiben der Daten selbst nicht sehen kann. Daher können vorherige Schreibvorgänge nicht über diesen Schreibvorgang hinausgehen.

Sagen wir, dass einige Daten bereit sind. Es ist zwingend erforderlich, dass alle Lesevorgänge, die ich nach dem Lesen nach dem Lesen ausgeführt habe, dass die Daten fertig waren. Nachfolgende Lesevorgänge können sich also nicht hinter diesem Lesevorgang bewegen.

Wenn Sie also einen synchronisierten Schreibvorgang durchführen, müssen Sie normalerweise sicherstellen, dass alle Schreibvorgänge, die Sie zuvor ausgeführt haben, für jeden sichtbar sind, der den synchronisierten Schreibvorgang sieht. Und wenn Sie einen synchronisierten Lesevorgang durchführen, ist es in der Regel unbedingt erforderlich, dass Lesevorgänge, die Sie danach ausführen, nach dem synchronisierten Lesevorgang stattfinden.

Oder, um es anders auszudrücken, ein Acquire liest normalerweise, dass Sie auf die Ressource zugreifen können, und nachfolgende Lese- und Schreibvorgänge dürfen nicht davor verschoben werden. In einer Version wird normalerweise geschrieben, dass Sie mit der Ressource fertig sind, und vorherige Schreibvorgänge dürfen nicht nach der Ressource verschoben werden.

-1

std::memory_order_acquire Zaun stellt nur sicher, all Last Betrieb nach dem Zaun nicht vor jedem Last Betrieb vor dem Zaun neu geordnet wird, so memory_order_acquirenicht gewährleisten die Speicher für andere Threads sichtbar sind, wenn nach Lasten ausgeführt werden. Aus diesem Grund wird memory_order_acquire nicht für den Geschäftsbetrieb unterstützt, Sie benötigen möglicherweise memory_order_seq_cst, um den Erwerb des Geschäfts zu erreichen.

Als Alternative können Sie

sagen
x.store(10, std::memory_order_releaxed); 
x.load(std::memory_order_acquire); // this introduce a data dependency 

alle Lasten nicht vor dem Laden neu geordnet zu gewährleisten. Auch hier funktioniert der Zaun nicht.

Außerdem könnte die Speicherordnung im atomaren Betrieb billiger als ein Speicherzaun sein, weil es nur die Ordnung relativ zum atomaren Befehl gewährleistet, nicht alle Anweisungen vor und nach dem Zaun.

Siehe auch formal description und explanation für Details.

+0

Der erste Satz ist nicht ganz richtig (-1). Tatsächlich kann * jeder Speicherzugriff *, der einem Erfassungszaun folgt, nicht mit einer Ladeoperation neu angeordnet werden, die diesem Zaun vorausgeht. (Umgekehrt kann jeder Speicherzugriff, der einem Freigabezaun vorausgeht, nicht mit einer diesem Zaun folgenden Speicheroperation neu geordnet werden.) –

+0

@JohnWickerson Tatsächlich sorgt 'memory_order_releaxed' nur dafür, dass nach dem Zaun nach einer atomaren Operation oder einem Zaun mit' memory_order_release' geladen wird. Es bietet keine Bestellung in Geschäften nach dem Zaun. Siehe Atomic-Fence-Synchronisation in [atomic_thread_fence] (http://en.cppreference.com/w/cpp/atomic/atomic_thread_fence) – user1887915

+0

Interessant! Ich glaube, dass die cppreference.com Website, auf die Sie sich beziehen, hier falsch ist. Nach dem offiziellen C11-Standard verhalten sich Release- und Acquise-Fences wie beschrieben. –