2016-07-30 9 views
2

Wie bekannt ist, gibt es 6 std::memory_order's und 2 seine:Gibt es irgendwelche Auswirkungen auf die Operationen mit den Variablen unabhängig von Atomlast verbrauchen?

  • acquire-semantische für Lasten - vermeidet Neuordnungs Last-Load und Lade-Speichere
  • Release-semantische für den Speicher - vermeidet Neuordnungs Shop -Speichern und Lade-Speichere

enter image description here

Ie

static std::atomic<int> X; 
static int L, S; 
... 

void thread_func() 
{ 
    int local1 = L; // load(L)-load(X) - !!! can be reordered with X !!! 
    S = local1;  // store(S)-load(X) - !!! can be reordered with X !!! 

    int x_local = X.load(std::memory_order_acquire); // load(X) 

    int local2 = L; // load(X)-load(L) - can't be reordered with X 
    S = local2;  // load(X)-store(S) - can't be reordered with X 
} 

Aber welche von Nachbestellungen über load(X) kann konsumieren-semantische sein: für acquire-semantische, nur S = local1; kann nach X.load(std::memory_order_acquire); ausgeführt werden?

static std::atomic<int *> X; 
static int L1, L2, S1, S2; 
static int L, S; 
... 

void thread_func() 
{ 
    int *x_ptr_local = new int(1); 


    int local1 = L1; // load(L1)-load(X) - !!! can be reordered with X !!! 
    S1 = local1;  // store(S1)-load(X) - !!! can be reordered with X !!! 

    int dependent_x1 = *x_ptr_local; // load(x_ptr)-load(X) - !!! can be reordered with X !!! 
    S = dependent_x1;     // store(S)-load(X) - !!! can be reordered with X !!! 

    x_ptr_local = X.load(std::memory_order_consume); // load(X) 

    int dependent_x2 = *x_ptr_local; // load(X)-load(x_ptr) - can't be reordered with X 
    S = dependent_x2;     // load(X)-store(S) - can't be reordered with X 

    int local2 = L2; // load(X)-load(L2) - !!! can be reordered with X !!! 
    S2 = local2;  // load(X)-store(S2) - !!! can be reordered with X !!! 
} 

Ist es wahr, dass nur Operationen mit dependent_x2 nicht über X.load(std::memory_order_consume) neu geordnet werden können?

Und alle Operationen mit Variablen L1, L2, S1, S2 und dependent_x1 kann über X.load(std::memory_order_consume) erst nachbestellt werden - das heißt kann entweder vor oder nach X.load(std::memory_order_consume) durchgeführt werden, ist es nicht?

+0

Warum konnte 'int local1 = L;' im ersten Beispiel nicht unter 'x_local' angeordnet werden? – 2501

+0

@ 2501 ** (1) ** 'int local1 = L;' kann nicht neu geordnet werden ** (2) ** 'int x_local = X.load (std :: memory_order_acquire); ', weil beide - Loads, und' std :: memory_order_acquire' verhindern Load-Load-Neuordnung - wie auf dem Bild gezeigt, nicht wahr? – Alex

+1

Ich sehe nur ** 2 ** Semantik zu erwerben, so wie 'S = local1;' kann unter ** 2 **, so kann ** 1 ** gehen. Was ist das Besondere an ** 1 **? – 2501

Antwort

3

memory_order_consume wird verwendet, um die Reihenfolge der Datenabhängigkeiten vom atomaren Objekt selbst zu erhalten, ohne eine schwerere Synchronisation wie die von memory_order_acquire eingeführte zu verwenden. Mit memory_order_acquire, alle Speicheroperationen nach der acquire - abhängig von der atomaren Variablen oder sonst - sind verboten, vor ihm neu bestellt, während memory_order_consume nur die Neuordnung von abhängigen Anweisungen hemmt. Dies ist vorteilhaft für schwächer geordnete Architekturen wie ARM und PowerPC, die die Anordnung von datenabhängigen Anweisungen ohne explizite Barriere garantieren.

Seit memory_order_consume befasst sich mit Datenabhängigkeiten, die meisten Anwendungsfälle davon beinhalten eine std::atomic<T*>. Producer-Threads können eine vollständige Datenstruktur erstellen und die Adresse dieser Datenstruktur unter Verwendung von memory_order_release im Atomzeiger veröffentlichen. Consumer-Threads laden dann den Atomzeiger mit memory_order_consume und können eine Datenabhängigkeit mit den Speichern des Writerthreads herstellen, wenn sie den Zeiger datenabhängig verwenden, z. B. durch Dereferenzierung. Der Standard garantiert, dass alle abhängigen Lasten die Speicher des Writer-Threads widerspiegeln. Da die Ladung der atomaren Variablen über memory_order_consume erfolgt, können jedoch keine Garantien für den Status unabhängiger Variablen aus der Perspektive des Leserthreads abgegeben werden.

In Ihrem ersten Beispiel kann keine der Lasten nach memory_order_acquire vor ihm neu geordnet werden. In Ihrem zweiten Beispiel ist jedoch jede Neuordnung, die nicht von X oder dem geladenen Wert davon abhängig ist, faires Spiel. Nämlich, int dependent_x2 = *x_ptr_local; (und die entsprechende Last von dependent_x2) sind garantiert in Bezug auf X geordnet bleiben, aber das ist es. Alle anderen Nachbestellungen sind möglich.

+0

Vielen Dank! Du schreibst auch über "datenabhängige Art und Weise, wie durch Dereferenzierung", dh "std :: memory_order_consume" macht den gesamten zugewiesenen Speicherbereich sichtbar, auf den der Zeiger mit consume-semantic zeigt, selbst wenn consum-pointer auf die Mitte zeigt des Arrays, dann wird das ganze Array sichtbar sein? Wird es funktionieren? 'int arr [1000000]; std :: atomic ptr; '** thread-1: **' arr [0] = 255; arr [999999] = 127; ptr.store (arr + 500000, std :: memory_order_release); '** thread-2: **' int * p; while (! (p = ptr.load (std :: memory_order_consume))); std :: cout << * (p) << "," << * (p + 499999); ' – Alex

+1

@Alex, Ich werde meine Antwort klären, aber weil Sie den geladenen Wert von' ptr' verwenden, um ein zu berechnen Offset in ein Array, es ist im Wesentlichen das gleiche wie die Verwendung als Dereferenzierung, und die Datenabhängigkeit gilt. Beachten Sie jedoch: Wenn Sie stattdessen versuchen würden, 'asser (arr [999999] == 127)' zu setzen, wäre dies nicht wahr, weil Sie keine Datenabhängigkeit zwischen dem gelesenen 'ptr' und den erfolgten Schreibvorgängen herstellen auf der produzierenden Seite. – Alejandro

+0

Meinst du das ** thread-2: ** 'asser (p [999999] == 127)' wird scheitern? Aber das ist UB, weil "p" in ** thread-2 ** gegenüber dem Zeiger "arr" von ** thread-1 ** um + 500000 verschoben ist und über die Grenzen hinausgeht. Wenn Sie meinen, daß ** thread-2: ** 'asser (p [499999] == 127)' scheitern wird, warum kann das passieren? Im Beispiel für die Bestellung von Release-Consume: void consumer() {std :: string * p2; while (! (P2 = ptr.load (std :: memory_order_consume))); assert (p2 [4] == 'o'); 'es wird nie falsch sein: http://en.cppreference.com/w/cpp/atomic/memory_order – Alex