2009-04-10 5 views
6

Ich renne in eine Situation, wo ich die atomare Summe von zwei Werten im Speicher brauche. Der Code, den ich geerbt geht so:Gibt es einen Weg, wie ich zwei atomare Lesevorgänge machen kann?

int a = *MemoryLocationOne; 
memory_fence(); 
int b = *MemoryLocationTwo; 
return (a + b) == 0; 

Die einzelnen liest aus a und b sind atomar, und alle Schreibvorgänge an anderer Stelle im Code zu diesen beiden Speicherplätze sind auch Atom Lockless. Das Problem besteht jedoch darin, dass sich die Werte der beiden Standorte zwischen den beiden Lesevorgängen ändern können.

Also wie mache ich diese Operation atomar? Ich weiß alles über CAS, aber es neigt dazu, Lese-Modifizier-Schreib-Operationen nur atomar zu machen, und das ist nicht ganz das, was ich hier machen möchte.

Gibt es eine Möglichkeit, dies zu tun, oder ist es die beste Option, den Code zu refaktorieren, so dass ich nur einen Wert überprüfen muss?

Edit: Danke, ich habe nicht erwähnt, dass ich das in der ersten Revision sperrig machen wollte, aber einige Leute haben es nach meiner zweiten Revision aufgegriffen. Ich weiß, dass niemand Leuten glaubt, wenn sie solche Dinge sagen, aber ich kann Schlösser praktisch nicht benutzen. Ich müsste einen Mutex mit Atomics emulieren, und das wäre mehr Arbeit als das Refactoring des Codes, um einen Wert anstelle von zwei zu verfolgen.

Für jetzt meine Methode der Untersuchung nutzt Ausnutzen der Tatsache, dass die Werte konsekutiv sind und greifen sie atomar mit einem 64-Bit-Lesen, die ich bin atomare auf meinen Zielplattformen sicher. Wenn jemand neue Ideen hat, bitte trage dazu bei! Vielen Dank.

Antwort

3

Wenn Sie wirklich brauchen, um sicherzustellen, dass a und b nicht ändern, während Sie diesen Test machen, dann müssen Sie für die gleiche Synchronisation verwenden, um alle Zugang zu a und b. Das ist deine einzige Wahl. Jeder Lesevorgang und jeder Schreibvorgang für einen dieser Werte muss denselben Speicherzaun, Synchronizer, Semaphor, Zeitschlitzsperre oder einen anderen verwendeten Mechanismus verwenden.

Damit können Sie sicherstellen, dass, wenn Sie:

memory_fence_start(); 
int a = *MemoryLocationOne; 
int b = *MemoryLocationTwo; 
int test = (a + b) == 0; 
memory_fence_stop(); 

return test; 

dann a nicht ändern, während Sie b lesen. Aber wieder müssen Sie den gleichen Synchronisationsmechanismus für alle Zugriff auf a und b verwenden.

Um eine spätere Bearbeitung Ihrer Frage zu reflektieren, die Sie für eine Lock-Free-Methode suchen, ist es völlig abhängig von dem Prozessor, den Sie verwenden und wie lange a und b sind und ob diese Speicherorte sind aufeinander folgend und richtig ausgerichtet.

Angenommen, diese sind im Speicher aufeinanderfolgend und jeweils 32 Bits, und Ihr Prozessor hat eine atomare 64-Bit-Lesen, dann können Sie eine atomare 64-Bit lesen, um die beiden Werte in lesen, analysieren die beiden Werte aus der 64-Bit-Wert, rechnen Sie und geben Sie zurück, was Sie zurückgeben möchten.Angenommen, Sie benötigen nie eine atomare Aktualisierung auf "a und b zur gleichen Zeit", sondern nur atomische Updates auf "a" oder auf "b" isoliert, dann wird dies tun, was Sie ohne Sperren wollen.

+0

Sie passieren aufeinander folgenden 32-Bit-Adressen sein und die Prozessoren Ich muss arbeiten, um Code auf haben Atom-64-Bit, wenn sie richtig ausgerichtet liest so Das sieht nach dem Weg aus. –

+0

Ah, in diesem Fall können Sie entweder eine Assembly schreiben oder einfach die Assembly-Ausgabe Ihres Compilers überprüfen, um zu überprüfen, ob ein 64-Bit-Typ das Richtige tut. In diesem Fall können Sie sperrenfrei gehen. – Eddie

3

Sie müssen sicherstellen, dass überall, wo einer der beiden Werte gelesen oder geschrieben wurde, sie von einer Speicherbarriere (Sperre oder kritischer Abschnitt) umgeben waren.

// all reads... 
lock(lockProtectingAllAccessToMemoryOneAndTwo) 
{ 
    a = *MemoryLocationOne; 
    b = *MemoryLocationTwo; 
} 

...

// all writes... 
lock(lockProtectingAllAccessToMemoryOneAndTwo) 
{ 
    *MemoryLocationOne = someValue; 
    *MemoryLocationTwo = someOtherValue; 
} 
1

Es gibt wirklich keine Möglichkeit, dies ohne ein Schloss zu tun. Wie ich weiß, haben keine Prozessoren eine doppelte atomare Ablesung.

+0

Selbst wenn ein Prozessor einen doppelten atomaren Lesevorgang hatte, würde das nur helfen, wenn die zwei betreffenden Speicheradressen richtig ausgerichtet und aufeinanderfolgend waren. – Eddie

+0

Nein, ein Prozessor könnte ein atomares Lesen von zwei beliebigen Speicherorten erlauben. – Zifre

+0

Mir ist kein Prozessor bekannt, der ein garantiert atomares Lesen nicht aufeinanderfolgender beliebiger Speicherorte ermöglicht, selbst in einer Multiprozessorumgebung, aber ich nehme dein Wort dafür. – Eddie

3

Wenn Sie x86 anvisieren, können Sie die 64-Bit-Vergleichs-/Exchange-Unterstützung verwenden und beide Int in ein einzelnes 64-Bit-Wort verpacken.

Unter Windows würden Sie dies tun:

// Skipping ensuring padding. 
union Data 
{ 
    struct members 
    { 
     int a; 
     int b; 
    }; 

    LONGLONG _64bitData; 
}; 

Data* data; 


Data captured; 

do 
{ 
    captured = *data; 
    int result = captured.members.a + captured.members.b; 
} while (InterlockedCompareExchange64((LONGLONG*)&data->_64bitData, 
        captured._64BitData, 
        captured._64bitData) != captured._64BitData); 

Wirklich hässlich. Ich würde vorschlagen, ein Schloss zu verwenden - viel wartbarer.

EDIT: Um die einzelnen Teile zu aktualisieren und zu lesen:

data->members.a = 0; 
fence(); 

data->members.b = 0; 
fence(); 

int captured = data->members.a; 

int captured = data->members.b; 
+0

Das ist wahr, aber Sie sind geschraubt, wenn Sie nur eines dieser Elemente aktualisieren müssen. –

+1

Nein, Sie führen atomare Lese-/Schreibvorgänge für die 32-Bit-Teile durch. – Michael

+1

Und falls es nicht klar war, war meine Antwort, zu zeigen, wie es gemacht werden könnte - ich empfehle immer, lockfreie Algorithmen zu vermeiden. – Michael