Wysłuchałem i przeczytałem kilka artykułów, rozmów i pytań o przepełnienie stosu std::atomic
i chciałbym mieć pewność, że dobrze to zrozumiałem. Ponieważ nadal jestem trochę mylony z linią pamięci podręcznej zapisuje widoczność z powodu możliwych opóźnień w protokołach koherencji pamięci podręcznej MESI (lub pochodnych), buforach pamięci, unieważnianiu kolejek i tak dalej.
Przeczytałem, że x86 ma silniejszy model pamięci i że jeśli opóźnienie unieważnienia pamięci podręcznej jest opóźnione, x86 może przywrócić rozpoczęte operacje. Ale teraz interesuje mnie tylko to, co powinienem założyć jako programista C ++, niezależnie od platformy.
[T1: wątek 1 T2: wątek 2 V1: wspólna zmienna atomowa]
Rozumiem, że std :: atomic gwarantuje, że,
(1) Żadne wyścigi danych nie występują w zmiennej (dzięki wyłącznemu dostępowi do linii pamięci podręcznej).
(2) W zależności od używanego przez nas uporządkowania pamięci, gwarantuje (z barierami), że zachodzi sekwencyjna spójność (przed barierą, po barierze lub obu).
(3) Po zapisie atomowym (V1) na T1, atomowa RMW (V1) na T2 będzie spójna (jego linia pamięci podręcznej zostanie zaktualizowana o zapisaną wartość na T1).
Ale jak wspomniałem elementarz koherencji pamięci podręcznej ,
Wszystkie te rzeczy implikują to, że domyślnie ładunki mogą pobierać nieaktualne dane (jeśli odpowiednie żądanie unieważnienia znajdowało się w kolejce unieważnień)
Czy więc poniższe informacje są prawidłowe?
(4) std::atomic
NIE gwarantuje, że T2 nie odczyta „przestarzałej” wartości odczytu atomowego (V) po zapisie atomowym (V) na T1.
Pytania, czy (4) ma rację: jeśli zapis atomowy na T1 unieważnia linię pamięci podręcznej bez względu na opóźnienie, dlaczego T2 czeka na unieważnienie, gdy operacja atomowa RMW, ale nie na odczyt atomowy?
Pytania, czy (4) jest błędne: kiedy wątek może odczytać „przestarzałą” wartość i „jest widoczny” w wykonaniu, a następnie?
Bardzo doceniam twoje odpowiedzi
Aktualizacja 1
Wygląda więc na to, że się myliłem (3). Wyobraź sobie następujący przeplot dla początkowej V1 = 0:
T1: W(1)
T2: R(0) M(++) W(1)
Chociaż w tym przypadku RMW T2 ma miejsce całkowicie po W (1), nadal może odczytać „przestarzałą” wartość (myliłem się). Zgodnie z tym atomic nie gwarantuje pełnej spójności pamięci podręcznej, tylko spójność sekwencyjną.
Aktualizacja 2
(5) Teraz wyobraź sobie ten przykład (x = y = 0 i są atomowe):
T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");
zgodnie z tym, o czym rozmawialiśmy, wyświetlenie „msg” wyświetlanego na ekranie nie dałoby nam informacji poza tym, że T2 zostało wykonane po T1. Mogło się zatem zdarzyć jedno z następujących wykonań:
- T1 <T3 <T2
- T1 <T2 <T3 (gdzie T3 widzi x = 1, ale jeszcze nie y = 1)
czy to prawda?
(6) Jeśli wątek zawsze może odczytać „przestarzałe” wartości, co by się stało, gdybyśmy przyjęli typowy scenariusz „publikowania”, ale zamiast sygnalizować, że niektóre dane są gotowe, robimy coś przeciwnego (usunąć dane)?
T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();
gdzie T2 nadal używałby usuniętego ptr, dopóki nie zobaczy, że is_enabled ma wartość false.
(7) Ponadto fakt, że wątki mogą odczytać „nieaktualne” wartości, oznacza, że muteksu nie można wdrożyć za pomocą tylko jednego atomowego prawa bez blokady? Wymagałoby to mechanizmu synchronizacji między wątkami. Czy wymagałby atomowej blokady?