Warum leidet Code, der eine gemeinsam genutzte Variable über Threads verändert, scheinbar NICHT unter einer Race-Bedingung?
Bin ich mit Cygwin-GCC und diesen code ausführen:
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
unsigned u = 0;
void foo()
{
u++;
}
int main()
{
vector<thread> threads;
for(int i = 0; i < 1000; i++) {
threads.push_back (thread (foo));
}
for (auto& t : threads) t.join();
cout << u << endl;
return 0;
}
Zusammengestellt, mit der Zeile: g++ -Wall -fexceptions -g -std=c++14 -c main.cpp -o main.o
.
Druckt es 1000, das ist richtig. Aber ich erwartete eine geringere Anzahl aufgrund des threads überschreiben eines bereits erhöhten Wert. Warum funktioniert dieser code nicht leiden, von den gegenseitigen Zugang?
Meiner test-Maschine hat 4 Kerne, und ich habe keine Beschränkungen auf das Programm, das ich kenne.
Das problem weiterhin besteht, wenn anstelle der Inhalt der freigegebenen foo
mit etwas komplexeren, z.B.
if (u % 3 == 0) {
u += 4;
} else {
u -= 1;
}
InformationsquelleAutor der Frage mafu | 2017-01-23
Du musst angemeldet sein, um einen Kommentar abzugeben.
foo()
ist so kurz, dass jeder thread wohl beendet ist, bevor die nächste bekommt man sogar hervorgebracht. Wenn Sie hinzufügen ein Schlaf für eine zufällige Zeit infoo()
vor deru++
können Sie beginnen, zu sehen, was Sie erwarten.InformationsquelleAutor der Antwort Rob K
Ist es wichtig zu verstehen, dass eine race condition garantiert nicht, der code wird nicht korrekt ausgeführt, lediglich, dass es etwas tun konnte, wie er ist ein nicht definiertes Verhalten. Darunter laufen als erwartet.
Besonders auf X86-und AMD64-Maschinen-race-Bedingungen in einigen Fällen verursachen selten Probleme, da viele der Anweisungen sind die atomaren und die Kohärenz gewährleistet sind sehr hoch. Diese Garantien sind etwas reduziert auf multi-Prozessor-Systeme, bei denen die lock-Präfix benötigt wird, für viele Anweisungen werden atomar.
Wenn auf Ihrem Computer Inkrement ist eine Atomare op, wird dies wahrscheinlich korrekt ausgeführt, obwohl nach der Sprache standard es ist Undefiniertes Verhalten.
Speziell erwarte ich auch in diesem Fall kann der Quelltext kompiliert werden, um eine Atomare Fetch und Hinzufügen Unterricht (HINZUFÜGEN oder XADD in X86-assembly), die ist in der Tat atomic im single-Prozessor-Systeme, können jedoch auf Multiprozessor-Systemen ist dies nicht garantiert atomar sind und eine Sperre erforderlich wäre, es so zu machen. Wenn Sie auf einem Multiprozessor-system gibt es ein Fenster, wo die threads stören könnte und zu falschen Ergebnissen führen.
Ich speziell kompiliert Ihren code, um die Montage mit https://godbolt.org/ und
foo()
kompiliert:Dies bedeutet, es ist allein die Durchführung einer add-Anweisung, die für einen einzelnen Prozessor atomic (obwohl, wie oben erwähnt, nicht so für ein multi-Prozessor-system).
InformationsquelleAutor der Antwort Vality
Ich denke, es ist nicht so sehr die Sache, wenn Sie einen Schlaf vor oder nach der
u++
. Es ist vielmehr so, dass der Betriebu++
übersetzt code, der - im Vergleich zu dem overhead, der Laich-threads, call -foo
- sehr schnell durchgeführt, so dass es unwahrscheinlich ist, zu bekommen abgefangen. Jedoch, wenn Sie "verlängern" die operationu++
dann ist die race-condition werden viel mehr wahrscheinlich:Ergebnis:
694
BTW: ich habe auch versucht
und es gab mir die meisten mal
1997
aber manchmal1995
.InformationsquelleAutor der Antwort Stephan Lechner
Es nicht leiden, von einer race condition. Setzen
usleep(1000);
voru++;
imfoo
und ich sehe anderen Ausgang (< 1000) jedes mal.InformationsquelleAutor der Antwort juf
Die wahrscheinliche Antwort, warum die race-condition nicht manifest für Sie, obwohl es hat vorhanden ist, ist, dass
foo()
ist so schnell, im Vergleich zu der Zeit es braucht, um eine Diskussion starten, dass jeder thread beendet, bevor die nächste überhaupt beginnen kann. Aber...Sogar mit Ihrem original-version, das Ergebnis variiert von system: ich habe versucht, es auf eine (quad-core) Macbook, und in zehn läuft, hab ich 1000 mal drei, 999 sechs mal, und einmal 998. Also die Rasse ist eher selten, aber deutlich vorhanden.
Zusammengestellt mit
'-g'
die einen Weg, um Fehler verschwinden. Ich kompiliert dein code immer noch unverändert, aber ohne die'-g'
und das Rennen wurde sehr viel stärker ausgeprägt: ich habe 1000 mal, 999 dreimal, 998 zweimal, 997 zweimal, 996 einmal, und 992 einmal.Re. der Vorschlag, das hinzufügen ein Schlaf -- das hilft, aber (a) einer festen Zeit schlafen lässt die threads noch verzerrt durch start-Zeitpunkt (vorbehaltlich der timer-Auflösung), und (b) eine zufällige schlafen, breitet Sie aus, wenn was wir wollen ist, ziehen Sie Sie näher zusammen. Stattdessen würde ich code Sie warten, bis ein start-signal, so kann ich Sie alle, bevor wir Sie an die Arbeit. Mit dieser version (mit oder ohne
'-g'
), bekomme ich alle Ergebnisse über Ort, so niedrig wie 974, und nicht höher als 998:InformationsquelleAutor der Antwort dgould