Antworten:
Nein, sie sind nicht dasselbe. Sie sind keine Teilmenge voneinander. Sie sind auch weder die notwendige noch die ausreichende Voraussetzung für einander.
Die Definition eines Datenrennens ist ziemlich klar und daher kann seine Erkennung automatisiert werden. Ein Datenwettlauf tritt auf, wenn zwei Anweisungen von verschiedenen Threads auf denselben Speicherort zugreifen. Mindestens einer dieser Zugriffe ist ein Schreibvorgang, und es gibt keine Synchronisation, die eine bestimmte Reihenfolge zwischen diesen Zugriffen vorschreibt .
Eine Rennbedingung ist ein semantischer Fehler. Es ist ein Fehler, der im Timing oder in der Reihenfolge der Ereignisse auftritt und zu einem fehlerhaften Programmverhalten führt. Viele Rennbedingungen können durch Datenrennen verursacht werden, dies ist jedoch nicht erforderlich.
Betrachten Sie das folgende einfache Beispiel, in dem x eine gemeinsam genutzte Variable ist:
Thread 1 Thread 2
lock(l) lock(l)
x=1 x=2
unlock(l) unlock(l)
In diesem Beispiel sind die Schreibvorgänge von Thread 1 und 2 in x durch Sperren geschützt. Daher erfolgen sie immer in einer Reihenfolge, die durch die Reihenfolge erzwungen wird, in der die Sperren zur Laufzeit erfasst werden. Das heißt, die Atomizität der Schriften kann nicht gebrochen werden; Es gibt immer ein Ereignis, bevor die Beziehung zwischen den beiden Schreibvorgängen in einer Ausführung erfolgt. Wir können einfach nicht wissen, welches Schreiben a priori vor dem anderen stattfindet.
Es gibt keine feste Reihenfolge zwischen den Schreibvorgängen, da Sperren dies nicht bereitstellen können. Wenn die Korrektheit der Programme beeinträchtigt ist, z. B. wenn auf das Schreiben von Thread 2 auf x gefolgt von dem Schreiben auf x in Thread 1 folgt, liegt eine Racebedingung vor, obwohl technisch gesehen kein Datenrennen vorliegt.
Es ist weitaus nützlicher, Rennbedingungen zu erkennen als Datenrennen. Dies ist jedoch auch sehr schwer zu erreichen.
Das umgekehrte Beispiel zu konstruieren ist ebenfalls trivial. Dieser Blog-Beitrag erklärt den Unterschied auch sehr gut anhand eines einfachen Beispiels für Bankgeschäfte.
Laut Wikipedia wird der Begriff "Race Condition" seit den Tagen der ersten elektronischen Logikgatter verwendet. Im Kontext von Java kann sich eine Race-Bedingung auf eine beliebige Ressource beziehen, z. B. eine Datei, eine Netzwerkverbindung, einen Thread aus einem Thread-Pool usw.
Der Begriff "Datenrennen" ist am besten seiner spezifischen Bedeutung vorbehalten, die vom JLS definiert wird .
Der interessanteste Fall ist eine Rennbedingung, die einem Datenrennen sehr ähnlich ist, aber immer noch keine ist, wie in diesem einfachen Beispiel:
class Race {
static volatile int i;
static int uniqueInt() { return i++; }
}
Da i
es volatil ist, gibt es kein Datenrennen; Unter dem Gesichtspunkt der Programmkorrektheit gibt es jedoch eine Racebedingung aufgrund der Nichtatomarität der beiden Operationen: Lesen i
, Schreiben i+1
. Mehrere Threads können denselben Wert von erhalten uniqueInt
.
data race
in JLS tatsächlich bedeutet?
Nein, sie sind unterschiedlich und keiner von ihnen ist eine Teilmenge von eins oder umgekehrt.
Der Begriff Race Condition wird häufig mit dem zugehörigen Term Data Race verwechselt, der auftritt, wenn die Synchronisation nicht verwendet wird, um den gesamten Zugriff auf ein gemeinsam genutztes nicht endgültiges Feld zu koordinieren. Sie riskieren ein Datenrennen, wenn ein Thread eine Variable schreibt, die möglicherweise als nächstes von einem anderen Thread gelesen wird, oder eine Variable liest, die möglicherweise zuletzt von einem anderen Thread geschrieben wurde, wenn beide Threads keine Synchronisation verwenden. Code mit Datenrassen hat keine nützliche definierte Semantik unter dem Java-Speichermodell. Nicht alle Rennbedingungen sind Datenrennen, und nicht alle Datenrennen sind Rennbedingungen, aber beide können dazu führen, dass gleichzeitige Programme auf unvorhersehbare Weise fehlschlagen.
Entnommen aus dem hervorragenden Buch - Java Concurrency in Practice von Joshua Bloch & Co.
TL; DR: Die Unterscheidung zwischen Datenrennen und Rennbedingungen hängt von der Art der Problemformulierung ab und davon, wo die Grenze zwischen undefiniertem Verhalten und genau definiertem, aber unbestimmtem Verhalten gezogen werden soll. Die derzeitige Unterscheidung ist konventionell und spiegelt am besten die Schnittstelle zwischen Prozessorarchitekt und Programmiersprache wider.
1. Semantik
Datenrennen bezieht sich speziell auf die nicht synchronisierten widersprüchlichen "Speicherzugriffe" (oder Aktionen oder Operationen) auf denselben Speicherort. Wenn es keinen Konflikt bei den Speicherzugriffen gibt, während es immer noch ein unbestimmtes Verhalten gibt, das durch die Reihenfolge der Operationen verursacht wird, ist dies eine Racebedingung.
Hinweis "Speicherzugriffe" haben hier eine bestimmte Bedeutung. Sie beziehen sich auf die "reinen" Speicherlade- oder Speicheraktionen, ohne dass zusätzliche Semantik angewendet wird. Beispielsweise weiß ein Speicher eines Threads (nicht unbedingt), wie lange es dauert, bis die Daten in den Speicher geschrieben sind, und wird schließlich an einen anderen Thread weitergegeben. In einem anderen Beispiel garantiert ein Speicher an einem Speicherort vor einem anderen Speicher an einem anderen Speicherort durch denselben Thread nicht (notwendigerweise), dass die ersten in den Speicher geschriebenen Daten vor den zweiten liegen. Infolgedessen kann die Reihenfolge dieser reinen Speicherzugriffe nicht (notwendigerweise) "begründet" werden , und es kann alles passieren, sofern nichts anderes genau definiert ist.
Wenn die "Speicherzugriffe" in Bezug auf die Reihenfolge durch Synchronisation gut definiert sind, kann eine zusätzliche Semantik sicherstellen, dass ihre Reihenfolge durch die Synchronisationen "begründet" werden kann, selbst wenn der Zeitpunkt der Speicherzugriffe unbestimmt ist . Beachten Sie, dass die Reihenfolge zwischen den Speicherzugriffen zwar begründet werden kann, sie jedoch nicht unbedingt bestimmt sind, daher die Race-Bedingung.
2. Warum der Unterschied?
Aber wenn die Reihenfolge im Rennzustand noch unbestimmt ist, warum sollte man sich dann die Mühe machen, sie vom Datenrennen zu unterscheiden? Der Grund liegt eher in der Praxis als in der Theorie. Dies liegt daran, dass in der Schnittstelle zwischen Programmiersprache und Prozessorarchitektur unterschieden wird.
Ein Speicherlade- / Speicherbefehl in einer modernen Architektur wird normalerweise als "reiner" Speicherzugriff implementiert, aufgrund der Art der Pipeline außerhalb der Reihenfolge, der Spekulation, der Cache-Ebene auf mehreren Ebenen, der CPU-RAM-Verbindung, insbesondere der Multi-Core-Verbindung usw. Es gibt viele Faktoren, die zu einem unbestimmten Zeitpunkt und einer unbestimmten Reihenfolge führen. Das Erzwingen der Reihenfolge für jeden Speicherbefehl ist mit einem enormen Nachteil verbunden, insbesondere bei einem Prozessordesign, das Multi-Core unterstützt. Daher wird die Ordnungssemantik mit zusätzlichen Anweisungen wie verschiedenen Barrieren (oder Zäunen) versehen.
Datenrennen ist die Situation der Ausführung von Prozessorbefehlen ohne zusätzliche Zäune, um die Reihenfolge widersprüchlicher Speicherzugriffe zu begründen. Das Ergebnis ist nicht nur unbestimmt, sondern möglicherweise auch sehr seltsam, z. B. können zwei Schreibvorgänge an dieselbe Wortposition durch unterschiedliche Threads mit jeder Schreibhälfte des Wortes resultieren oder nur mit ihren lokal zwischengespeicherten Werten arbeiten. - Dies ist aus Sicht des Programmierers undefiniertes Verhalten. Aber sie sind (normalerweise) aus Sicht des Prozessorarchitekten gut definiert.
Programmierer haben einen Weg haben , um die Vernunft ihre Code - Ausführung. Datenrennen sind etwas, das sie nicht sinnvoll machen können, daher sollten sie (normalerweise) immer vermieden werden. Aus diesem Grund definieren die Sprachspezifikationen, die niedrig genug sind, Datenrennen normalerweise als undefiniertes Verhalten, das sich vom genau definierten Speicherverhalten der Rassenbedingungen unterscheidet.
3. Sprachgedächtnismodelle
Unterschiedliche Prozessoren können ein unterschiedliches Speicherzugriffsverhalten aufweisen, dh ein Prozessorspeichermodell. Für Programmierer ist es schwierig, das Speichermodell jedes modernen Prozessors zu studieren und dann Programme zu entwickeln, die von ihnen profitieren können. Es ist wünschenswert, wenn die Sprache ein Speichermodell definieren kann, so dass sich die Programme dieser Sprache immer wie erwartet verhalten, wie es das Speichermodell definiert. Aus diesem Grund sind in Java und C ++ die Speichermodelle definiert. Es liegt in der Verantwortung der Compiler- / Laufzeitentwickler, sicherzustellen, dass die Sprachspeichermodelle über verschiedene Prozessorarchitekturen hinweg erzwungen werden.
Das heißt, wenn eine Sprache das Verhalten des Prozessors auf niedriger Ebene nicht offenlegen möchte (und bereit ist, bestimmte Leistungsvorteile der modernen Architekturen zu opfern), können sie ein Speichermodell definieren, das die Details von "rein" vollständig verbirgt. Speicherzugriffe, aber Anwenden der Ordnungssemantik für alle Speicheroperationen. Dann können die Compiler- / Laufzeitentwickler entscheiden, jede Speichervariable in allen Prozessorarchitekturen als flüchtig zu behandeln. Für diese Sprachen (die gemeinsam genutzten Speicher über Threads hinweg unterstützen) gibt es keine Datenrennen, es können jedoch auch bei einer Sprache mit vollständiger sequentieller Konsistenz Rennbedingungen vorliegen.
Andererseits kann das Prozessorspeichermodell strenger (oder weniger entspannt oder auf einer höheren Ebene) sein, z. B. indem eine sequentielle Konsistenz implementiert wird, wie dies bei frühen Prozessoren der Fall war. Dann werden alle Speicheroperationen geordnet, und es gibt kein Datenrennen für Sprachen, die im Prozessor ausgeführt werden.
4. Fazit
Zurück zur ursprünglichen Frage, IMHO ist es in Ordnung, Datenrennen als Sonderfall der Rennbedingung zu definieren, und Rennbedingungen auf einer Ebene können zu Datenrennen auf einer höheren Ebene werden. Dies hängt von der Art der Problemformulierung ab und davon, wo die Grenze zwischen undefiniertem Verhalten und genau definiertem, aber unbestimmtem Verhalten gezogen werden soll. Nur die aktuelle Konvention definiert die Grenze an der Sprach-Prozessor-Schnittstelle, bedeutet nicht unbedingt, dass dies immer der Fall ist und sein muss; Die derzeitige Konvention spiegelt jedoch wahrscheinlich am besten die hochmoderne Schnittstelle (und Weisheit) zwischen Prozessorarchitekt und Programmiersprache wider.