Frage

Heute habe ich über diese Frage kam:

Sie haben einen Code

static int counter = 0;
void worker() {
    for (int i = 1; i <= 10; i++)
        counter++;
}

Wenn worker würde von zwei verschiedenen Threads aufgerufen werden, welcher Wert wird counter haben, nachdem sie beide fertig sind?

Ich weiß, dass tatsächlich könnte es alles sein. Aber meine inneren Eingeweide mir sagen, wird diese counter++ höchstwahrscheinlich in einzelnen Assemblerbefehl übersetzt werden, und wenn beide Threads auf dem gleichen Kern auszuführen sind, wird counter 20 sein.

Was aber, wenn diese Fäden auf verschiedene Kern oder Prozessoren ausgeführt werden, könnte es eine Race-Bedingung in ihrem Mikro sein? Ist ein Assemblerbefehl immer als atomare Operation angesehen werden könnte?

War es hilfreich?

Lösung

Speziell für x86, und in Bezug auf Ihr Beispiel: counter++, gibt es eine Reihe von Möglichkeiten, es kompiliert werden kann. Das triviale Beispiel ist:

inc counter

Dies schlägt sich in den folgenden Mikrooperationen:

  • load counter zu einem verborgenen Register auf der CPU
  • erhöht das Register
  • Speichern Sie die aktualisierte Register in counter

Dies ist im Wesentlichen der gleiche wie:

mov eax, counter
inc eax
mov counter, eax

Beachten Sie, dass, wenn einige andere Agent-Updates zwischen der Last und den Laden counter, es wird nicht nach dem Laden in counter widerspiegeln. Dieses Mittel könnte ein anderer Thread in dem gleichen Kern, ein weiterer Kern in der gleichen CPU, eine andere CPU im selben System oder sogar einige externe Mittel, die DMA verwendet (Direct Memory Access).

Wenn Sie sicherstellen wollen, dass dieser inc atomar ist, verwenden Sie den lock Präfix:

lock inc counter

lock gewährleistet, dass niemand counter zwischen der Last und dem Laden aktualisieren.


kompliziertere Anweisungen in Bezug auf, können Sie in der Regel nicht davon ausgehen, dass sie atomar ausgeführt werden soll, wenn sie den lock Präfix unterstützen.

Andere Tipps

Die Antwort ist: es hängt

Hier finden Sie einige Verwirrung um, was ein Assembler-Befehl ist. Normalerweise wird ein Assemblerbefehl in genau einem Maschinenbefehl übersetzt. Die excemption ist, wenn Sie Makros verwenden -., Aber Sie sollten sich bewusst sein,

Das heißt, kocht die Frage nach unten ist ein Maschinenbefehl Atom?

In den guten alten Zeiten, es war. Aber heute, mit komplexen CPUs, langen Laufe Anweisungen, Hyperthreading, ... es ist nicht. Einige CPUs Garantie, dass einige Erhöhen / Verringern Anweisungen sind atomar. Der Grund dafür ist, dass sie für sehr einfache Synchonisation ordentlich sind.

Auch einige CPU-Befehle sind nicht so problematisch. Wenn Sie eine einfache haben holen (von einem Stück von Daten, die der Prozessor in einem Stück holen kann) - holen die sich natürlich atomar ist, weil es nichts ist überhaupt geteilt werden. Aber wenn Sie nicht ausgerichtete Daten haben, ist es kompliziert wird wieder.

Die Antwort lautet: Es hängt davon ab. die Maschine Bedienungsanleitung des Verkäufers aufmerksam zu lesen. Im Zweifelsfall ist es nicht!

Edit: Oh, ich habe es jetzt, Sie auch für ++ Zähler fragen. Die Aussage „höchstwahrscheinlich übersetzt werden“ können nicht alle trauen. Dieser weitgehend hängt auch von den Compiler natürlich! Schwieriger wird es, wenn der Compiler verschiedene Optimierungen macht.

Nicht immer -. Auf einigen Architekturen eine Montageanleitung in eine Maschinencodebefehl übersetzt wird, während auf anderen es nicht

Zusätzlich - Sie können nie gehen davon aus, dass die Programmsprache Sie verwenden, ist eine scheinbar einfache Codezeile in eine Montageanleitung zu kompilieren. Darüber hinaus ist auf einigen Architekturen, können Sie nicht ein Maschinencode übernehmen atomar ausführen wird.

Verwenden Sie geeignete Synchronisationstechniken statt, abhängig von der Sprache, in denen Sie Codierung.

  1. Anhebung / Absenkung Operationen auf 32-Bit oder weniger Integer-Variablen auf einem einzigen 32-Bit-Prozessor ohne Hyper-Threading-Technologie sind atomar.
  2. Auf einem Prozessor mit Hyper-Threading-Technologie oder auf einem Multi-Prozessor-System werden die Inkrement / Dekrement-Operationen nicht garantiert atomicaly ausgeführt werden.

für ungültig erklärt Nathan Kommentar: Wenn ich meine Intel x86-Assembler richtig erinnere, nur der INC-Befehl für die Register arbeitet und arbeitet nicht direkt für Speicherplatz.

So ein Zähler ++ würde nicht einen einzigen Befehl in Assembler sein (nur den Nachinkrement Teil ignoriert wird). Es würde mindestens drei Befehle sein: Lastzählervariable zurück zu Zählern zu registrieren, Inkrementregisters, laden registrieren. Und das ist nur für x86-Architektur.

Kurz gesagt, sich nicht darauf verlassen Atom zu sein, wenn sie von der Sprachspezifikation angegeben ist und dass der Compiler, die Sie verwenden unterstützt die Spezifikationen.

Nein, Sie können nicht davon ausgehen. Es sei denn, es eindeutig in Compiler-Spezifikation angegeben. Und außerdem kann niemand garantieren, dass ein einziger Assemblerbefehl tatsächlich atomar. In der Praxis wird jeder Assemblerbefehl Anzahl der Mikro Betrieb übersetzt - Uops.
Auch die Frage der Race-Bedingung ist eng gekoppelt mit Speichermodell (Kohärenz, aufeinander folgend, löst Kohärenz und etc.), für jeden die Antwort und das Ergebnis anders sein könnten.

Ein weiteres Problem ist, dass, wenn Sie nicht die Variable als volatile deklarieren, erzeugt der Code würde wahrscheinlich nicht auf den Speicher aktualisieren bei jedem Schleifendurchlauf, erst am Ende der Schleife der Speicher aktualisiert werden würde.

Könnte nicht eine tatsächliche Antwort auf Ihre Frage, aber (unter der Annahme, das ist C # oder eine andere .NET-Sprache), wenn Sie counter++ wollen Atom-Multi-Threaded wirklich sein, Sie System.Threading.Interlocked.Increment(counter) nutzen könnten.

Sehen Sie andere Antworten für aktuelle Informationen über die vielen verschiedenen Möglichkeiten, warum / wie counter++ nicht atomar sein könnte. ; -)

In den meisten Fällen nicht . In der Tat, auf x86, können Sie den Befehl

ausführen
push [address]

, die in C, würde wie etwas sein:

*stack-- = *address;

Dies führt zwei Speicherübertragungen in einem Befehl .

Das ist im Grunde unmöglich in 1 Taktzyklus zu tun, nicht zuletzt, weil ein Speichertransfer ist auch nicht möglich, in einem Zyklus!

Auf vielen anderen Prozessoren ist die Trennung zwischen Speichersystem und Prozessor größer. (Oft dieser Prozessor kann wenig oder seine Big-Endian je nach Speichersystem, wie ARM und PowerPC), dies hat auch Folgen für das Atom Verhalten, wenn der Systemspeicher neu ordnen kann liest und schreibt.

Zu diesem Zweck gibt es Speicherbarrieren ( http://en.wikipedia.org/wiki/ Memory_barrier )

Also kurz gesagt, während atomare Befehle auf Intel genug sind (mit den entsprechenden Lock-Präfixen), mehr muss auf Nicht-Intel erfolgen, da die Speicher-I / O nicht in der gleichen Reihenfolge sein könnte.

Dies ist ein bekanntes Problem, wenn auf andere Architekturen „Lock-frei“ Lösungen von Intel portieren.

(Beachten Sie, dass Multi-Prozessor (nicht Multi-Core-Systeme) auf x86 scheinen auch Speicherbarrieren zu müssen, zumindest im 64-Bit-Modus.

Ich glaube, dass Sie eine Race-Bedingung für den Zugang erhalten werden.

Wenn Sie eine atomare Operation in inkrementierenden Zähler, um sicherzustellen, wollte dann müssen Sie würde ++ Zähler verwenden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top