W dokumentacji std::memory_order
na cppreference.com jest przykład swobodnego zamawiania:
Zrelaksowane zamawianie
Oznaczone operacje atomowe
memory_order_relaxed
nie są operacjami synchronizacji; nie narzucają kolejności między dostępami do pamięci jednocześnie. Gwarantują one tylko atomowość i spójność kolejności modyfikacji.Na przykład, gdy xiy początkowo wynoszą zero,
// Thread 1: r1 = y.load(std::memory_order_relaxed); // A x.store(r1, std::memory_order_relaxed); // B // Thread 2: r2 = x.load(std::memory_order_relaxed); // C y.store(42, std::memory_order_relaxed); // D
wolno wytwarzać r1 == r2 == 42, ponieważ chociaż A jest sekwencjonowane przed B w wątku 1, a C jest sekwencjonowane przed D w wątku 2, nic nie stoi na przeszkodzie, aby D pojawił się przed A w kolejności modyfikacji y, a B z występujące przed C w kolejności modyfikacji x. Efekt uboczny D na y może być widoczny dla obciążenia A w gwincie 1, podczas gdy efekt uboczny B na x może być widoczny dla obciążenia C w gwincie 2. W szczególności może to wystąpić, jeśli D zostanie zakończone przed C w wątek 2, z powodu zmiany kolejności kompilatora lub w czasie wykonywania.
mówi „C jest sekwencjonowane przed D w wątku 2”.
Zgodnie z definicją sekwencjonowania przed, którą można znaleźć w kolejności oceny , jeśli A jest sekwencjonowane przed B, wówczas ocena A zostanie zakończona przed rozpoczęciem oceny B. Ponieważ C jest sekwencjonowane przed D w wątku 2, C musi zostać zakończone przed rozpoczęciem D, dlatego część warunku ostatniego zdania migawki nigdy nie będzie spełniona.
źródło
Odpowiedzi:
Wierzę, że cpreferencje są słuszne. Myślę, że sprowadza się to do zasady „jak gdyby” [intro. Wykonanie] / 1 . Kompilatory są zobowiązane jedynie do odtworzenia obserwowalnego zachowania programu opisanego przez twój kod. Sekwencjonowano, zanim stosunek ustala się tylko od oceny pod kątem wątku, w którym są realizowane te oceny [intro.execution] / 15 . Oznacza to, że gdy dwie oceny sekwencyjnie jedna po drugiej pojawiają się gdzieś w jakimś wątku, kod faktycznie działający w tym wątku musi zachowywać się tak, jakby wszystko , co robi pierwsza ocena, rzeczywiście wpłynęło na wszystko, co robi druga ocena. Na przykład
musi wydrukować 42. Jednak kompilator nie musi tak naprawdę przechowywać wartości 42 w obiekcie
x
przed odczytaniem wartości z tego obiektu, aby go wydrukować. Równie dobrze może pamiętać, że ostatnia przechowywana wartośćx
wynosiła 42, a następnie po prostu wydrukowała wartość 42 bezpośrednio przed wykonaniem rzeczywistego zapisu wartości 42 dox
. W rzeczywistości, jeślix
jest zmienną lokalną, może równie dobrze śledzić, jaką wartość ta zmienna została ostatnio przypisana w dowolnym momencie i nigdy nawet nie tworzyć obiektu ani faktycznie przechowywać wartości 42. Wątek nie ma możliwości odróżnienia. Zachowanie jest zawsze będzie tak, jakby to była zmienna i jak gdyby wartość 42 faktycznie przechowywane w obiekciex
przedładowane z tego obiektu. Ale to nie znaczy, że wygenerowany kod maszynowy musi faktycznie przechowywać i ładować cokolwiek w dowolnym miejscu. Wszystko, co jest wymagane, to aby obserwowalne zachowanie wygenerowanego kodu maszynowego było nierozróżnialne od tego, jakie byłoby zachowanie, gdyby wszystkie te rzeczy faktycznie się wydarzyły.Jeśli spojrzymy na
wtedy tak, C jest sekwencjonowane przed D. Ale patrząc z tego wątku w oderwaniu, nic, co C robi, nie wpływa na wynik D. I nic, co robi D, nie zmienia wyniku C. Jedyny sposób, w jaki jeden może wpłynąć na drugi, to jako pośrednia konsekwencja czegoś, co dzieje się w innym wątku. Jednak, określając
std::memory_order_relaxed
, wyraźnie oświadczyłeśkolejność, w jakiej obciążenie i przechowywanie są obserwowane przez inny wątek, jest nieistotna. Ponieważ żaden inny wątek nie może obserwować obciążenia i przechowywać w określonej kolejności, nic innego nie może zrobić, aby C i D oddziaływały na siebie w spójny sposób. Zatem kolejność, w jakiej ładowanie i przechowywanie są faktycznie wykonywane, nie ma znaczenia. Dzięki temu kompilator może dowolnie zmieniać ich kolejność. I, jak wspomniano w wyjaśnieniu pod tym przykładem, jeśli magazyn z D jest wykonywany przed ładowaniem z C, wówczas r1 == r2 == 42 może rzeczywiście dojść do…źródło
Czasami możliwe jest uporządkowanie akcji względem dwóch innych sekwencji akcji, bez sugerowania względnego uporządkowania akcji w tych sekwencjach względem siebie.
Załóżmy na przykład, że jedno ma następujące trzy zdarzenia:
a odczyt p2 jest niezależnie uporządkowany po zapisie p1 i przed zapisem p3, ale nie ma szczególnej kolejności, w której uczestniczą zarówno p1, jak i p3. W zależności od tego, co jest zrobione z p2, kompilator może opóźnić p1 za p3 i nadal osiągnąć wymaganą semantykę za pomocą p2. Załóżmy jednak, że kompilator wiedział, że powyższy kod był częścią większej sekwencji:
W takim przypadku może ustalić, że może zmienić kolejność sklepu na p1 po powyższym kodzie i skonsolidować go z następującym sklepem, w wyniku czego powstanie kod, który zapisuje p3 bez uprzedniego napisania p1:
Chociaż może się wydawać, że zależności danych spowodowałyby, że pewne części relacji sekwencjonowania zachowywałyby się przejściowo, kompilator może zidentyfikować sytuacje, w których pozorne zależności danych nie istnieją, a zatem nie miałby oczekiwanych efektów przechodnich.
źródło
Jeśli są dwie instrukcje, kompilator wygeneruje kod w kolejności sekwencyjnej, więc kod dla pierwszego zostanie umieszczony przed drugim. Ale procesory cpus wewnętrznie mają potoki i mogą równolegle uruchamiać operacje asemblacyjne. Instrukcja C jest instrukcją ładowania. Podczas pobierania pamięci potok przetworzy kilka następnych instrukcji i biorąc pod uwagę, że nie są one zależne od instrukcji ładowania, mogą one zostać wykonane przed zakończeniem C (np. Dane dla D znajdowały się w pamięci podręcznej, C w pamięci głównej).
Jeśli użytkownik naprawdę potrzebował wykonania dwóch instrukcji sekwencyjnie, można zastosować surowsze operacje porządkowania pamięci. Zasadniczo użytkownicy nie dbają o to, dopóki program jest logicznie poprawny.
źródło
Cokolwiek myślisz, jest równie ważne. Standard nie mówi, co wykonuje sekwencyjnie, a czego nie i jak można to pomieszać .
Od ciebie i każdego programisty zależy stworzenie spójnej semantyki nad tym bałaganem, dzieło godne wielu doktoratów.
źródło