müssen Integer-Lesevorgänge kritische Abschnitte geschützt sein?

8

Ich habe in C ++ 03 einen Code gefunden, der diese Form annimmt:

%Vor%

Muss das Lesen von foo_.a geschützt werden? z.B.:

%Vor%

Wenn ja, warum?

Bitte gehen Sie davon aus, dass die Ganzzahlen 32-Bit-ausgerichtet sind. Die Plattform ist ARM.

    
PaulH 30.11.2012, 18:16
quelle

5 Antworten

9

Technisch ja, aber nicht auf vielen Plattformen. Nehmen wir als erstes an, dass int 32 Bits ist (was ziemlich häufig, aber nicht annähernd universell ist).

Es ist möglich, dass die zwei Wörter (16 Bit Teile) eines 32 Bit int separat gelesen oder geschrieben werden. Auf einigen Systemen werden sie separat gelesen, wenn% code_% nicht richtig ausgerichtet ist.

Stellen Sie sich ein System vor, bei dem Sie nur 32-Bit-32-Bit-Lese- und -Schreibvorgänge (und 16-Bit-Lese- und Schreiboperationen mit 16 Bit) und eine int , die eine solche Grenze überspannt, ausführen können. Anfangs ist int gleich Null (dh int )

Ein Thread schreibt 0x00000000 in den 0xBAADF00D , der andere liest ihn "zur gleichen Zeit".

Der Schreib-Thread schreibt zuerst int in das hohe Wort von 0xBAAD . Der Leser-Thread liest dann die gesamte int (sowohl hoch als auch niedrig) und erhält int - was ein Zustand ist, in den die 0xBAAD0000 nie absichtlich eingefügt wurde!

Der Writer-Thread schreibt dann das niedrige Wort int .

Wie bereits erwähnt, sind auf einigen Plattformen alle 32-Bit-Lese- / Schreibvorgänge atomar, weshalb dies kein Problem darstellt. Es gibt jedoch andere Bedenken.

Der meiste Code zum Sperren / Entsperren enthält Anweisungen für den Compiler, um eine Neuanordnung der Sperre zu verhindern. Ohne diese Verhinderung der Neuordnung ist der Compiler frei, Dinge neu zu ordnen, solange er sich "wie-wenn" in einem einzigen Thread-Kontext verhält, der auf diese Weise funktioniert hätte. Wenn Sie 0xF00D und dann a im Code lesen, könnte der Compiler b lesen, bevor er b liest, solange darin keine In-Thread-Chance für a angezeigt wird Intervall.

Möglicherweise verwendet der Code, den Sie gerade lesen, diese Sperren, um sicherzustellen, dass das Lesen der Variablen in der Reihenfolge geschieht, die im Code geschrieben ist.

Andere Probleme werden in den folgenden Kommentaren angesprochen, aber ich fühle mich nicht kompetent, sie anzugehen: Cache-Probleme und Sichtbarkeit.

    
Yakk 30.11.2012, 18:19
quelle
3

Wenn man sich dieses anschaut, scheint es so, als hätte der Arm das Speichermodell ziemlich entspannt, so dass man eine Form von Speicherbarriere braucht, um das sicherzustellen Schreibvorgänge in einem Thread sind sichtbar, wenn Sie sie in einem anderen Thread erwarten. Was Sie tun oder std :: atomic verwenden, ist wahrscheinlich auf Ihrer Plattform notwendig. Wenn Sie dies nicht berücksichtigen, können Sie feststellen, dass die Aktualisierungen in verschiedenen Threads nicht funktionieren, was Ihr Beispiel brechen würde.

    
jcoder 30.11.2012 18:33
quelle
2

Ich denke, Sie können C ++ 11 verwenden, um sicherzustellen, dass Integer-Leseoperationen atomar sind und (zum Beispiel) std::atomic<int> verwenden.

    
Bartek Banachewicz 30.11.2012 18:20
quelle
2

Der C ++ - Standard besagt, dass es ein Datenrennen gibt, wenn ein Thread gleichzeitig mit einem anderen Thread in eine Variable schreibt oder wenn zwei Threads gleichzeitig in dieselbe Variable schreiben. Es sagt weiter, dass ein Datenrennen undefiniertes Verhalten erzeugt. Also müssen Sie diese Lese- und Schreibvorgänge synchronisieren.

Es gibt drei separate Probleme, wenn ein Thread Daten liest, die von einem anderen Thread geschrieben wurden. Erstens gibt es ein Tearing: Wenn das Schreiben mehr als einen einzelnen Buszyklus erfordert, ist es möglich, dass ein Threadwechsel mitten in der Operation auftritt, und ein anderer Thread könnte einen halbgeschriebenen Wert sehen; Es gibt ein analoges Problem, wenn ein Lesen mehr als einen einzelnen Buszyklus erfordert. Zweitens gibt es Transparenz: Jeder Prozessor hat seine eigene lokale Kopie der Daten, an denen er in letzter Zeit gearbeitet hat, und das Schreiben in den Cache eines Prozessors aktualisiert nicht unbedingt den Cache eines anderen Prozessors. Drittens gibt es Compiler-Optimierungen, die das Lesen und Schreiben auf eine Weise umordnen, die innerhalb eines einzelnen Threads in Ordnung wäre, aber Multi-Threaded-Code bricht. Thread-sicherer Code muss sich mit allen drei Problemen befassen. Das ist die Aufgabe von Synchronisations-Primitiven: Mutexe, Zustandsvariablen und Atomics.

    
Pete Becker 30.11.2012 19:45
quelle
0

Obwohl die Integer-Lese / Schreib-Operation höchstwahrscheinlich atomar ist, werden die Compiler-Optimierungen und der Prozessor-Cache immer noch Probleme verursachen, wenn Sie es nicht richtig machen.

Um zu erklären - der Compiler nimmt normalerweise an, dass der Code single-threaded ist und viele Optimierungen macht, die darauf basieren. Zum Beispiel könnte es die Reihenfolge der Anweisungen ändern. Oder, wenn es sieht, dass die Variable nur geschrieben und nie gelesen wird, könnte sie es vollständig optimieren.

Die CPU speichert diese Ganzzahl auch zwischen, wenn also ein Thread sie schreibt, sieht die andere möglicherweise erst viel später.

Es gibt zwei Dinge, die Sie tun können. Einer besteht darin, wie in Ihrem ursprünglichen Code in einen kritischen Abschnitt einzudringen. Das andere ist, die Variable als volatile zu markieren. Das wird dem Compiler signalisieren, dass auf diese Variable von mehreren Threads zugegriffen wird, und eine Reihe von Optimierungen deaktivieren, sowie spezielle Cachesync-Anweisungen (auch "Speicherbarrieren" genannt) um Zugriffe auf die Variable (oder so wie ich es verstehe) setzen. Anscheinend ist das falsch.

Hinzugefügt: Wie bereits in einer anderen Antwort erwähnt, verfügt Windows über Interlocked APIs, die verwendet werden können, um diese Probleme für nicht volatile -Variablen zu vermeiden.

    
Vilx- 30.11.2012 18:27
quelle