Ich habe zwei Threads, einen, der ein Int aktualisiert und einen, der es liest. Dies ist ein statistischer Wert, bei dem die Reihenfolge der Lese- und Schreibvorgänge keine Rolle spielt.
Meine Frage ist, muss ich den Zugriff auf diesen Multi-Byte-Wert trotzdem synchronisieren? Oder anders ausgedrückt, kann ein Teil des Schreibvorgangs abgeschlossen sein und unterbrochen werden, und dann geschieht das Lesen.
Stellen Sie sich beispielsweise einen Wert = 0x0000FFFF vor, der den inkrementierten Wert 0x00010000 erhält.
Gibt es eine Zeit, in der der Wert wie 0x0001FFFF aussieht, über die ich mir Sorgen machen sollte? Je größer der Typ, desto wahrscheinlicher ist es, dass so etwas passiert.
Ich habe diese Art von Zugriffen immer synchronisiert, war aber neugierig, was die Community denkt.
quelle
=
: stackoverflow.com/questions/8290768/…Antworten:
Zuerst könnte man denken, dass Lese- und Schreibvorgänge der nativen Maschinengröße atomar sind, aber es gibt eine Reihe von Problemen, mit denen man sich befassen muss, einschließlich der Cache-Kohärenz zwischen Prozessoren / Kernen. Verwenden Sie atomare Operationen wie Interlocked * unter Windows und das Äquivalent unter Linux. C ++ 0x wird eine "atomare" Vorlage haben, um diese in eine schöne und plattformübergreifende Oberfläche zu packen. Wenn Sie eine Plattformabstraktionsschicht verwenden, bietet diese möglicherweise diese Funktionen. ACE tut dies, siehe die Klassenvorlage ACE_Atomic_Op .
quelle
Junge, was für eine Frage. Die Antwort darauf lautet:
Auf die Architektur des Systems kommt es an. Auf einem IA32 ist eine korrekt ausgerichtete Adresse eine atomare Operation. Nicht ausgerichtete Schreibvorgänge können atomar sein. Dies hängt vom verwendeten Caching-System ab. Wenn der Speicher innerhalb einer einzelnen L1-Cache-Zeile liegt, ist er atomar, andernfalls nicht. Die Breite des Busses zwischen CPU und RAM kann die atomare Natur beeinflussen: Ein korrekt ausgerichteter 16-Bit-Schreibvorgang auf einem 8086 war atomar, während der gleiche Schreibvorgang auf einem 8088 nicht darauf zurückzuführen war, dass der 8088 nur einen 8-Bit-Bus hatte, während der 8086 einen hatte 16 Bit Bus.
Wenn Sie C / C ++ verwenden, vergessen Sie nicht, den gemeinsam genutzten Wert als flüchtig zu markieren. Andernfalls glaubt der Optimierer, dass die Variable in einem Ihrer Threads niemals aktualisiert wird.
quelle
Wenn Sie einen 4-Byte-Wert lesen / schreiben UND dieser im Speicher DWORD-ausgerichtet ist UND Sie auf der I32-Architektur arbeiten, sind Lese- und Schreibvorgänge atomar.
quelle
Ja, Sie müssen Zugriffe synchronisieren. In C ++ 0x handelt es sich um ein Datenrennen und ein undefiniertes Verhalten. Bei POSIX-Threads ist das Verhalten bereits undefiniert.
In der Praxis können schlechte Werte auftreten, wenn der Datentyp größer als die native Wortgröße ist. Außerdem wird in einem anderen Thread der geschriebene Wert möglicherweise nie angezeigt, da Optimierungen das Lesen und / oder Schreiben verschieben.
quelle
Sie müssen synchronisieren, aber auf bestimmten Architekturen gibt es effiziente Möglichkeiten, dies zu tun.
Verwenden Sie am besten Unterprogramme (möglicherweise hinter Makros maskiert), damit Sie Implementierungen bedingt durch plattformspezifische ersetzen können.
Der Linux-Kernel hat bereits einen Teil dieses Codes.
quelle
Unter Windows ist Interlocked *** Exchange *** Add garantiert atomar.
quelle
Um das zu wiederholen, was alle oben gesagt haben, kann die Sprache vor C ++ 0x nichts über den Zugriff auf gemeinsam genutzten Speicher von mehreren Threads garantieren. Alle Garantien liegen beim Compiler.
quelle
Nein, das sind sie nicht (oder zumindest kann man nicht davon ausgehen, dass sie es sind). Allerdings gibt es einige Tricks, um dies atomar zu tun, aber sie sind normalerweise nicht portabel (siehe Vergleichen und Tauschen ).
quelle
Ich stimme vielen und insbesondere Jason zu . Unter Windows würde man wahrscheinlich InterlockedAdd und seine Freunde verwenden.
quelle
Abgesehen von dem oben erwähnten Cache-Problem ...
Wenn Sie den Code auf einen Prozessor mit einer kleineren Registergröße portieren, ist er nicht mehr atomar.
IMO, Threading-Probleme sind zu heikel, um es zu riskieren.
quelle
Die einzige tragbare Möglichkeit besteht darin, den im Header signal.h definierten Typ sig_atomic_t für Ihren Compiler zu verwenden. In den meisten C- und C ++ - Implementierungen ist dies ein int. Deklarieren Sie dann Ihre Variable als "volatile sig_atomic_t".
quelle
Nehmen wir dieses Beispiel
int x; x++; x=x+5;
Die erste Anweisung wird als atomar angenommen, da sie in eine einzelne INC-Assembly-Direktive übersetzt wird, die einen einzelnen CPU-Zyklus benötigt. Die zweite Zuweisung erfordert jedoch mehrere Operationen, sodass es sich eindeutig nicht um eine atomare Operation handelt.
Ein anderes zB
x=5;
Auch hier müssen Sie den Code zerlegen, um zu sehen, was genau hier passiert.
quelle
x+=6
.tc, ich denke, sobald Sie eine Konstante (wie 6) verwenden, würde die Anweisung nicht in einem Maschinenzyklus abgeschlossen sein. Versuchen Sie, den Befehlssatz von x + = 6 im Vergleich zu x ++ zu sehen
quelle
Einige Leute denken, dass ++ c atomar ist, haben aber ein Auge auf die erzeugte Assembly. Zum Beispiel mit 'gcc -S':
movl cpt.1586(%rip), %eax addl $1, %eax movl %eax, cpt.1586(%rip)
Um ein int zu erhöhen, lädt der Compiler es zuerst in ein Register und speichert es wieder im Speicher. Das ist nicht atomar.
quelle
Auf jeden Fall NEIN! Diese Antwort von unserer höchsten C ++ - Autorität, M. Boost:
Operationen mit "normalen" Variablen sind garantiert nicht atomar.
quelle
arithmetic
Operationen, die aus einer Lese-, Aktualisierungs- und Schreibsequenz für "normale" Variablen bestehen, nicht atomar sind, nicht, obread
oder obwrite
Operationen für "gewöhnliche" Variablen atomar sind oder nicht.