Czy C ++ odczytuje i zapisuje int Atomic?


81

Mam dwa wątki, jeden aktualizujący int, a drugi czytający. Jest to wartość statystyczna, w przypadku której kolejność odczytów i zapisów nie ma znaczenia.

Moje pytanie brzmi, czy mimo wszystko muszę zsynchronizować dostęp do tej wartości wielobajtowej? Innymi słowy, część zapisu może zostać zakończona i przerwana, a następnie nastąpi odczyt.

Na przykład pomyśl o wartości = 0x0000FFFF, która otrzymuje zwiększoną wartość 0x00010000.

Czy jest czas, w którym wartość wygląda jak 0x0001FFFF, o który powinienem się martwić? Z pewnością im większy typ, tym bardziej prawdopodobne jest, że coś takiego może się wydarzyć.

Zawsze synchronizowałem tego typu dostępy, ale byłem ciekawy, co myśli społeczność.


5
Naprawdę? Nie obchodzi mnie, co myśli społeczność.
Zależy

1
Ciekawa lektura na ten temat: channel9.msdn.com/Shows/Going+Deep/…
ereOn

Odpowiedzi:


47

Na początku można by pomyśleć, że odczyty i zapisy rozmiaru maszyny natywnej są atomowe, ale istnieje wiele problemów, którymi należy się zająć, w tym spójność pamięci podręcznej między procesorami / rdzeniami. Użyj operacji atomowych, takich jak Interlocked * w systemie Windows i ich odpowiedników w systemie Linux. C ++ 0x będzie miał „atomowy” szablon do umieszczenia ich w ładnym i wieloplatformowym interfejsie. Na razie, jeśli używasz warstwy abstrakcji platformy, może ona zapewniać te funkcje. ACE tak, zobacz szablon klasy ACE_Atomic_Op .


Dokument ACE_Atomic_Op został przeniesiony - można go teraz znaleźć pod adresem dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ace/Atomic_Op.inl
Byron

63

Chłopcze, co za pytanie. Odpowiedź brzmi:

Tak, nie, hmmm, to zależy

Wszystko sprowadza się do architektury systemu. Na IA32 poprawnie wyrównany adres będzie operacją atomową. Niewyrównane zapisy mogą być atomowe, zależy to od używanego systemu buforowania. Jeśli pamięć znajduje się w pojedynczej linii pamięci podręcznej L1, to jest atomowa, w przeciwnym razie nie. Szerokość magistrali między procesorem a pamięcią RAM może wpływać na atomową naturę: prawidłowo wyrównany 16-bitowy zapis na 8086 był atomowy, podczas gdy ten sam zapis na 8088 nie był, ponieważ 8088 miał tylko 8-bitową magistralę, podczas gdy 8086 miał 16-bitowa magistrala.

Ponadto, jeśli używasz C / C ++, nie zapomnij oznaczyć współdzielonej wartości jako niestabilnej, w przeciwnym razie optymalizator uzna, że ​​zmienna nigdy nie zostanie zaktualizowana w jednym z twoich wątków.


23

5
@IngeHenriksen: Nie przekonuje mnie ten link.
Skizz,

inne źródło, ale niestety bardzo stare (poprzedza std :: atomic): web.archive.org/web/20190219170904/https://software.intel.com/…
Max Barraclough


9

Tak, musisz zsynchronizować dostęp. W C ++ 0x będzie to wyścig danych i niezdefiniowane zachowanie. W wątkach POSIX jest to już niezdefiniowane zachowanie.

W praktyce możesz otrzymać złe wartości, jeśli typ danych jest większy niż rodzimy rozmiar słowa. Ponadto inny wątek może nigdy nie zobaczyć zapisanej wartości z powodu optymalizacji przenoszącej odczyt i / lub zapis.


3

Musisz zsynchronizować, ale na niektórych architekturach są na to wydajne sposoby.

Najlepiej jest używać podprogramów (być może ukrytych za makrami), aby można było warunkowo zastąpić implementacje specyficznymi dla platformy.

Jądro Linuksa ma już część tego kodu.



2

Aby powtórzyć to, co wszyscy mówili na górze, język sprzed C ++ 0x nie gwarantuje niczego w zakresie dostępu do pamięci współdzielonej z wielu wątków. Wszelkie gwarancje zależałyby od kompilatora.


0

Nie, nie są (a przynajmniej nie możesz zakładać, że tak jest). Powiedziawszy to, istnieją pewne sztuczki, które pozwalają zrobić to niepodzielnie, ale zazwyczaj nie są one przenośne (zobacz Porównaj i zamień ).


0

Zgadzam się z wieloma, a zwłaszcza z Jasonem . W systemie Windows prawdopodobnie użyłbyś InterlockedAdd i jego przyjaciół.


0

Oprócz wspomnianego wyżej problemu z pamięcią podręczną ...

Jeśli przeniesiesz kod do procesora o mniejszym rozmiarze rejestru, nie będzie on już atomowy.

IMO, problemy z wątkami są zbyt trudne, aby ryzykować.



0

Weźmy ten przykład

int x;
x++;
x=x+5;

Zakłada się, że pierwsza instrukcja jest atomowa, ponieważ przekłada się na pojedynczą dyrektywę zestawu INC, która zajmuje jeden cykl procesora. Jednak drugie przypisanie wymaga kilku operacji, więc wyraźnie nie jest to operacja atomowa.

Inny np.

x=5;

Ponownie, musisz zdemontować kod, aby zobaczyć, co dokładnie się tutaj dzieje.


2
Ale kompilator może go zoptymalizować do x+=6.
tc.

0

tc, myślę, że w momencie, gdy użyjesz stałej (np. 6), instrukcja nie zostanie ukończona w jednym cyklu maszyny. Spróbuj zobaczyć zestaw instrukcji x + = 6 w porównaniu z x ++


0

Niektórzy myślą, że ++ c jest atomowe, ale mają oko na wygenerowany asembler. Na przykład z „gcc -S”:

movl    cpt.1586(%rip), %eax
addl    $1, %eax
movl    %eax, cpt.1586(%rip)

Aby zwiększyć liczbę int, kompilator najpierw ładuje ją do rejestru i zapisuje z powrotem w pamięci. To nie jest atomowe.


1
Nie stanowi to problemu, jeśli tylko jeden wątek zapisuje do zmiennej, ponieważ nie ma przerwania.
Ben Voigt,

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.