Jest atomową wartością bool do odczytu / zapisu w języku C #

84

Czy dostęp do pola bool jest atomowy w języku C #? W szczególności, czy muszę założyć blokadę:

class Foo
{
   private bool _bar;

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

   //... same for a read
   if (_bar) { ... }
}
dbkk
źródło
1
Tak, ale (prawdopodobnie) także tak. Tak, dostęp do / ustawienie pola bool jest atomowe, ALE operacja if nie jest (patrz odpowiedź Dror Helper poniżej), więc nadal możesz potrzebować blokady.
JPProgrammer,

Odpowiedzi:

119

Tak.

Odczyty i zapisy następujących typów danych są atomowe: typy bool, char, byte, sbyte, short, ushort, uint, int, float i reference.

zgodnie ze specyfikacją języka C # .

Edycja: prawdopodobnie warto również zrozumieć niestabilne słowo kluczowe.

Larsenal
źródło
10
Sam wskaźnik, po ponownym przypisaniu go, jest atomowy (tj. Foo foo1 = foo2;
user142350
4
@konfigurator: Pytanie brzmi, czego dokładnie chcesz. Programy bez blokad łatwo się mylą; więc jeśli naprawdę tego nie potrzebujesz, lepiej użyć prostszego frameworka (np. TPL). Innymi słowy, „niestabilny” nie jest błędem, ale oznaką podstępnego (tj. najlepiej unikanego) kodu. OP tak naprawdę nie powiedział, czego chce, po prostu waham się, czy polecić niestabilne chcenie nie chcąc.
Eamon Nerbonne,
4
Waw. To niebezpieczne sformułowanie, dla ludzi C ++ atomic oznacza, że ​​każdy odczyt i zapis jest również otoczony odpowiednim ogrodzeniem pamięci. Co z pewnością nie ma miejsca w C #. Ponieważ w przeciwnym razie wydajność byłaby okropna, ponieważ jest obowiązkowa dla wszystkich zmiennych <long. Atomic w żargonie C # wydaje się oznaczać, że kiedy odczyty lub zapisy w końcu się zdarzają, gwarantuje się, że nigdy nie będą w stanie zepsutym . Ale nie mówi nic o tym, kiedy jest „ostatecznie”.
v.oddou,
4
Jeśli zapis do int i long jest atomowy, to w przypadku użycia Interlocked.Add(ref myInt);np.?
Mike de Klerk
6
@MikedeKlerk Odczyt i zapis są atomowe, ale oddzielne. i++jest równe i=i+1, co oznacza, że ​​wykonujesz atomowy odczyt, następnie dodanie, a następnie atomowy zapis. Inny wątek mógłby się zmienić ipo odczycie, ale przed zapisem. Na przykład dwa wątki działające i++jednocześnie na tym samym i mogą odczytać w tym samym czasie (i tym samym odczytać tę samą wartość), dodać jeden do niego, a następnie oba zapisują tę samą wartość, skutecznie dodając tylko raz. Zablokowane. Dodaj zapobiega temu. Zasadniczo fakt, że typ jest atomowy, jest przydatny tylko wtedy, gdy jest zapisany tylko jeden wątek, ale wiele wątków odczytuje.
Jannis Froese
49

Jak wspomniano powyżej, bool jest atomowy, ale nadal musisz pamiętać, że zależy to również od tego, co chcesz z nim zrobić.

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

nie jest operacją niepodzielną, co oznacza, że ​​wartość b może się zmienić, zanim bieżący wątek wykona kod po instrukcji if.

Dror Helper
źródło
29

Dostępy bool są rzeczywiście atomowe, ale to nie wszystko.

Nie musisz się martwić o odczytanie wartości, która jest „niekompletnie zapisana” - nie jest jasne, co to może oznaczać dla bool w każdym przypadku - ale musisz się martwić o pamięć podręczną procesora, przynajmniej jeśli szczegóły czas są problemem. Jeśli wątek # 1 działający na rdzeniu A ma twój _barw pamięci podręcznej i _barzostanie zaktualizowany przez wątek # 2 działający na innym rdzeniu, wątek # 1 nie zobaczy zmiany natychmiast, chyba że dodasz blokowanie, zadeklarujesz _barjako volatilelub jawnie wstawisz wywołania Thread.MemoryBarrier()do unieważnienia wartość w pamięci podręcznej.

McKenzieG1
źródło
1
„nie jest jasne, co to może oznaczać dla bool w każdym przypadku” Elementy, które istnieją tylko w jednym bajcie pamięci w atomic, ponieważ cały bajt jest zapisywany w tym samym czasie. W przeciwieństwie do elementów takich jak double, które istnieją w wielu bajtach, jeden bajt mógłby być zapisany przed drugim i można było zaobserwować zapisaną w połowie lokację pamięci.
MindStalker
3
MemoryBarrier () nie unieważnia żadnej pamięci podręcznej procesora. W niektórych architekturach procesor może zmieniać kolejność odczytów i zapisów w pamięci głównej w celu zwiększenia wydajności. Zmiana kolejności może się zdarzyć, o ile z punktu widzenia pojedynczego wątku semantyka pozostaje taka sama. Funkcja MemoryBarrier () żąda od procesora ograniczenia zmiany kolejności, tak aby operacje pamięci emitowane przed barierą nie były zmieniane w taki sposób, że kończyły się za barierą.
TiMoch
1
Bariera pamięci jest przydatna, jeśli tworzysz gruby obiekt i przełączasz do niego odwołanie, które może być odczytane z innych wątków. Bariera gwarantuje, że odniesienie nie zostanie zaktualizowane w pamięci głównej przed resztą obiektu tłuszczu. Gwarantuje się, że inne wątki nigdy nie zobaczą aktualizacji odniesienia, zanim obiekt fat nie będzie faktycznie dostępny w pamięci głównej. var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch
1

podejście, którego użyłem i uważam, że jest poprawne, to

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

celem było po prostu uniknięcie konieczności ciągłego blokowania obiektu w każdej iteracji, tylko po to, aby sprawdzić, czy musimy go zablokować, aby zapewnić dużą ilość informacji o zmianie stanu, która występuje rzadko. Myślę, że to podejście działa. A jeśli wymagana jest absolutna spójność, myślę, że zmienna byłaby odpowiednia na b bool.

stux
źródło
4
Jest to rzeczywiście poprawne podejście do blokowania w ogóle, ale jeśli wartości logiczne są atomowe, łatwiej (i szybciej) jest pominąć blokadę.
dbkk
2
Bez blokady „duża zmiana stanu” nie zostanie wykonana atomowo. Zamek -> zestaw | metoda check -> lock -> check zapewni również, że kod „// inny” zostanie wykonany PRZED kodem „// inne rzeczy”. Zakładając, że sekcja „inny wątek” wykonuje iteracje wiele razy (tak jest w moim przypadku), przez większość czasu wystarczy sprawdzić wartość bool, ale w rzeczywistości nie uzyskuje się (prawdopodobnie rywalizowanej) blokady, jest to duża wygrana w wydajności
stux
1
Cóż, jeśli masz lock(), nie potrzebujesz volatile.
xmedeko,