Ist ein Bool-Lese- / Schreibatom in C #?

83

Ist der Zugriff auf ein Bool- Feld in C # atomar? Muss ich insbesondere ein Schloss schließen:

class Foo
{
   private bool _bar;

   //... in some function on any thread (or many threads)
   _bar = true;

   //... same for a read
   if (_bar) { ... }
}
dbkk
quelle
1
Ja, aber (möglicherweise) auch ja. Ja, der Zugriff auf / das Einstellen eines Bool-Felds ist atomar, ABER wenn dies nicht der Fall ist (siehe Antwort von Dror Helper unten), benötigen Sie möglicherweise auch noch eine Sperre.
JPProgrammer

Antworten:

118

Ja.

Lese- und Schreibvorgänge der folgenden Datentypen sind atomar: Bool-, Char-, Byte-, Sbyte-, Short-, Ushort-, Uint-, Int-, Float- und Referenztypen.

wie in C # Language Spec gefunden .

Bearbeiten: Es lohnt sich wahrscheinlich auch, das flüchtige Schlüsselwort zu verstehen .

Larsenal
quelle
10
Der Zeiger selbst, der ihn neu zuweist, ist atomar (dh Foo foo1 = foo2;
user142350
4
@configurator: Die Frage ist, was genau Sie wollen. Es ist leicht, sperrfreie Programme falsch zu machen. Wenn Sie es nicht wirklich benötigen, ist es besser, ein einfacheres Framework (z. B. die TPL) zu verwenden. 'flüchtig' ist mit anderen Worten nicht falsch, sondern ein Zeichen für kniffligen (dh vorzugsweise vermiedenen) Code. Der OP hat nicht wirklich gesagt, was er will, ich zögere nur, volatile wohl oder übel zu empfehlen .
Eamon Nerbonne
4
Waw. Dies ist eine gefährliche Formulierung, denn für C ++ - Leute bedeutet atomar, dass jedes Lesen und Schreiben auch von einem entsprechenden Speicherzaun umgeben ist. Was in C # sicherlich nicht der Fall ist. Denn sonst wäre die Leistung schrecklich, da sie für alle Variablen <long obligatorisch ist. Atomic hier in C # -Sprache scheint zu bedeuten, dass wenn die Lese- oder Schreibvorgänge irgendwann stattfinden, sie garantiert niemals in einem kaputten Zustand sind . Aber es sagt nichts darüber aus, wann "irgendwann" ist.
v.oddou
4
Wenn das Schreiben in int und long atomar ist, wenn dann Interlocked.Add(ref myInt);z.
Mike de Klerk
6
@MikedeKlerk Das Lesen und Schreiben ist atomar, aber getrennt. i++ist gleich i=i+1, was bedeutet, dass Sie ein atomares Lesen, dann eine Addition und dann ein atomares Schreiben durchführen. Ein anderer Thread könnte inach dem Lesen, aber vor dem Schreiben geändert werden. Zum Beispiel können zwei Threads, die i++gleichzeitig auf demselben i arbeiten, gleichzeitig lesen (und somit denselben Wert lesen), einen hinzufügen und dann beide denselben Wert schreiben, wobei effektiv nur einmal hinzugefügt wird. Interlocked.Add verhindert dies. In der Regel ist die Tatsache, dass ein Typ atomar ist, nur dann nützlich, wenn nur ein Thread geschrieben wird, aber viele Threads gelesen werden.
Jannis Froese
49

Wie oben erwähnt, ist bool atomar, aber Sie müssen sich immer noch daran erinnern, dass es auch davon abhängt, was Sie damit machen möchten.

if(b == false)
{
    //do something
}

ist keine atomare Operation, was bedeutet, dass sich der b-Wert ändern kann, bevor der aktuelle Thread den Code nach der if-Anweisung ausführt.

Dror Helfer
quelle
29

Bool-Zugriffe sind in der Tat atomar, aber das ist nicht die ganze Geschichte.

Sie müssen sich nicht um das Lesen eines Werts kümmern, der "unvollständig geschrieben" ist - es ist nicht klar, was dies möglicherweise für einen Bool bedeuten könnte -, aber Sie müssen sich um Prozessor-Caches kümmern, zumindest wenn Details von Timing sind ein Problem. Wenn Thread 1, der auf Kern A ausgeführt wird, Ihren _barCache hat und _barvon Thread 2, der auf einem anderen Kern ausgeführt wird, aktualisiert wird, wird Thread 1 die Änderung nicht sofort sehen, es sei denn, Sie fügen Sperren hinzu, deklarieren _barals volatileoder fügen explizit Aufrufe ein Thread.MemoryBarrier(), um den zu ungültig zu machen zwischengespeicherter Wert.

McKenzieG1
quelle
1
"Es ist nicht klar, was das für einen Bool auf jeden Fall bedeuten könnte." Elemente, die nur in einem Byte Speicher bei Atomic vorhanden sind, weil das gesamte Byte gleichzeitig beschrieben wird. Im Vergleich zu Elementen wie double, die in mehreren Bytes vorhanden sind, könnte ein Byte vor das andere Byte geschrieben werden, und Sie könnten einen halb geschriebenen Speicherort beobachten.
MindStalker
3
MemoryBarrier () macht keinen Prozessor-Cache ungültig. In einigen Architekturen kann der Prozessor Lese- und Schreibvorgänge für die Leistung in den Hauptspeicher umordnen. Eine Neuordnung kann erfolgen, solange aus Sicht eines einzelnen Threads die Semantik gleich bleibt. MemoryBarrier () fordert den Prozessor auf, die Neuordnung zu begrenzen, damit vor der Barriere ausgegebene Speicheroperationen nicht so neu angeordnet werden, dass sie nach der Barriere enden.
TiMoch
1
Eine Speicherbarriere ist nützlich, wenn Sie ein fettes Objekt erstellen und einen Verweis darauf wechseln, der möglicherweise von anderen Threads gelesen wird. Die Barriere garantiert, dass die Referenz nicht vor dem Rest des Fettobjekts im Hauptspeicher aktualisiert wird. Andere Threads sehen das Referenzupdate garantiert nie, bevor das fette Objekt tatsächlich im Hauptspeicher verfügbar ist. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch
1

Der Ansatz, den ich verwendet habe und den ich für richtig halte, ist

volatile bool b = false;

.. rarely signal an update with a large state change...

lock b_lock
{
  b = true;
  //other;
}

... another thread ...

if(b)
{
    lock b_lock
    {
       if(b)
       {
           //other stuff
           b = false;
       }
     }
}

Das Ziel bestand im Wesentlichen darin, zu vermeiden, dass ein Objekt bei jeder Iteration wiederholt gesperrt werden muss, um zu überprüfen, ob es gesperrt werden muss, um eine große Menge an Statusänderungsinformationen bereitzustellen, die selten vorkommen. Ich denke, dieser Ansatz funktioniert. Und wenn absolute Konsistenz erforderlich ist, denke ich, dass Volatilität auf dem B- Bool angemessen wäre.

stux
quelle
4
Dies ist in der Tat ein korrekter Ansatz zum Sperren im Allgemeinen, aber wenn Bools atomar sind, ist es einfacher (und schneller), die Sperre wegzulassen.
dbkk
1
Ohne die Sperre wird dann die "große Zustandsänderung" nicht atomar durchgeführt. Das Schloss -> setzen | Mit check -> lock -> check wird auch sichergestellt, dass der Code "// other" VOR dem Code "// other stuff" ausgeführt wird. Die „ein anderer Thread“ Abschnitt Unter der Annahme , oft iteriert (die es in meinem Fall ist), nur ein Bool zu überprüfen hat, die meiste Zeit, aber eine (möglicherweise behauptet) Sperre nicht tatsächlich zu erwerben, ist eine große Leistung gewinnen
stux
Nun, wenn Sie haben lock(), brauchen Sie nicht volatile.
Xmedeko