È un bool di lettura / scrittura atomico in C #


84

L'accesso a un campo bool è atomico in C #? In particolare, devo mettere un lucchetto in giro:

class Foo
{
   private bool _bar;

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

   //... same for a read
   if (_bar) { ... }
}


1
Sì, ma (forse) anche sì. Sì, l'accesso / l'impostazione di un campo bool è atomico, MA l'operazione if non lo è (fare riferimento alla risposta di Dror Helper di seguito), quindi potrebbe essere necessario anche un blocco.
JPProgrammer

Risposte:


119

Sì.

Le letture e le scritture dei seguenti tipi di dati sono atomiche: bool, char, byte, sbyte, short, ushort, uint, int, float e reference.

come si trova nelle specifiche del linguaggio C # .

Modifica: probabilmente vale anche la pena capire la parola chiave volatile .


10
Il puntatore stesso, riassegnandolo, è atomico (cioè Foo foo1 = foo2;
user142350

4
@configurator: la domanda è cosa vuoi esattamente. È facile sbagliare i programmi senza blocco; quindi a meno che tu non ne abbia davvero bisogno, è meglio usare un framework più semplice (ad esempio il TPL). "volatile" in altre parole non è sbagliato, ma un segno di codice complicato (cioè preferibilmente evitato). L'OP non ha davvero detto quello che vuole, sono solo riluttante a raccomandare volatile, volenti o nolenti.
Eamon Nerbonne

4
Waw. Questa è una formulazione pericolosa, per le persone in C ++ atomico significa che qualsiasi lettura e scrittura è anche circondata dal corrispondente recinto di memoria. Che sicuramente non è il caso in C #. Perché altrimenti la prestazione sarebbe orribile poiché è obbligatoria per tutte le variabili <lungo. Atomic qui nel linguaggio C #, sembra significare che quando le letture o le scritture alla fine avvengono, è garantito che non saranno mai in uno stato rotto . Ma non dice nulla su quando è "alla fine".
v.oddou

4
Se la scrittura su int e long è atomica, quando si usa Interlocked.Add(ref myInt);ad es?
Mike de Klerk

6
@MikedeKlerk La lettura e la scrittura sono atomiche, ma separate. i++è uguale a i=i+1, il che significa che fai una lettura atomica, quindi un'addizione, quindi una scrittura atomica. Un altro thread potrebbe modificare idopo la lettura ma prima della scrittura. Ad esempio, due thread che eseguono i++contemporaneamente sullo stesso i possono leggere contemporaneamente (e quindi leggere lo stesso valore), aggiungerne uno e quindi scrivere entrambi lo stesso valore, aggiungendo effettivamente solo una volta. Interlocked.Add lo impedisce. Come regola generale, il fatto che un tipo sia atomico è utile solo se c'è un solo thread in scrittura ma molti thread in lettura.
Jannis Froese

49

Come detto sopra, il bool è atomico ma devi comunque ricordare che dipende anche da cosa vuoi farci.

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

non è un'operazione atomica, il che significa che il valore b potrebbe cambiare prima che il thread corrente esegua il codice dopo l'istruzione if.


30

gli accessi bool sono effettivamente atomici, ma non è tutta la storia.

Non devi preoccuparti di leggere un valore che è 'scritto in modo incompleto' - in ogni caso non è chiaro cosa potrebbe significare per un bool - ma devi preoccuparti delle cache del processore, almeno se i dettagli di i tempi sono un problema. Se il thread n. 1 in esecuzione sul core A ha il tuo _barnella cache e _barviene aggiornato dal thread n. 2 in esecuzione su un altro core, il thread n. 1 non vedrà la modifica immediatamente a meno che tu non aggiunga il blocco, dichiari _barcome volatileo inserisca esplicitamente chiamate a Thread.MemoryBarrier()per invalidare il valore memorizzato nella cache.


1
"in ogni caso non è chiaro cosa ciò possa significare per un bool" Elementi che esistono in un solo byte di memoria in atomic perché l'intero byte viene scritto allo stesso tempo. Rispetto a elementi come double che esistono in più byte, un byte potrebbe essere scritto prima dell'altro byte e si potrebbe osservare una mezza posizione di memoria scritta.
MindStalker

3
MemoryBarrier () non invalida la cache del processore. In alcune architetture, il processore può riordinare le letture e le scritture nella memoria principale per le prestazioni. Il riordino può avvenire fintanto che dal punto di vista di un singolo thread, la semantica rimane la stessa. MemoryBarrier () richiede al processore di limitare il riordino in modo che le operazioni di memoria emesse prima della barriera non vengano riordinate in modo tale da finire dopo la barriera.
TiMoch

1
Una barriera di memoria è utile se si crea un oggetto grasso e si passa a un riferimento che può essere letto da altri thread. La barriera garantisce che il riferimento non venga aggiornato nella memoria principale prima del resto dell'oggetto grasso. È garantito che gli altri thread non vedranno mai l'aggiornamento di riferimento prima che l'oggetto fat sia effettivamente disponibile nella memoria principale. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch

1

l'approccio che ho usato, e penso sia corretto, lo è

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;
       }
     }
}

l'obiettivo era fondamentalmente quello di evitare di dover bloccare ripetutamente un oggetto a ogni iterazione solo per verificare se era necessario bloccarlo per fornire una grande quantità di informazioni sul cambiamento di stato che si verificano raramente. Penso che questo approccio funzioni. E se è richiesta una coerenza assoluta, penso che volatile sarebbe appropriato su b bool.


4
Questo è davvero un approccio corretto al blocco in generale, ma se i bool sono atomici, è più semplice (e più veloce) omettere il blocco.
dbkk

2
Senza il blocco, il "grande cambiamento di stato" non verrà eseguito in modo atomico. Il lucchetto -> imposta | check -> lock -> check approach assicurerà anche che il codice "// other" venga eseguito PRIMA del codice "// other stuff". Supponendo che la sezione "un altro thread" è iterazione molte volte (che è nel mio caso), solo dover controllare un bool, la maggior parte del tempo, ma in realtà non acquisire un blocco (possibilmente sostenuto), è una vittoria importante prestazioni
Stux

1
Bene, se lo hai lock(), non ne hai bisogno volatile.
xmedeko
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.