Czy wyjaśnienie swobodnego zamawiania jest błędne w preferencjach?


13

W dokumentacji std::memory_orderna cppreference.com jest przykład swobodnego zamawiania:

Zrelaksowane zamawianie

Oznaczone operacje atomowe memory_order_relaxednie 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.


Czy twoje pytanie dotyczy konkretnie C ++ 11?
ciekawy

nie, dotyczy to również c ++ 14,17. Wiem, że zarówno kompilator, jak i procesor mogą zmienić kolejność C w D. Jednak jeśli nastąpi zmiana kolejności, C nie będzie można ukończyć przed rozpoczęciem D. Myślę więc, że w zdaniu „Sekwencja A jest sekwencjonowana przed B w wątku 1, a C jest sekwencjonowana przed D w wątku 2”. Dokładniej jest powiedzieć „W kodzie A jest umieszczony PRZED B w wątku 1, a C jest umieszczony PRZED D w wątku 2”. Celem tego pytania jest potwierdzenie tej myśli
abigaile

Nic nie jest zdefiniowane w kontekście „zmiany kolejności”.
ciekawy

Odpowiedzi:


12

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

int x = 0;
x = 42;
std::cout << x;

musi wydrukować 42. Jednak kompilator nie musi tak naprawdę przechowywać wartości 42 w obiekcie xprzed odczytaniem wartości z tego obiektu, aby go wydrukować. Równie dobrze może pamiętać, że ostatnia przechowywana wartość xwynosiła 42, a następnie po prostu wydrukowała wartość 42 bezpośrednio przed wykonaniem rzeczywistego zapisu wartości 42 do x. W rzeczywistości, jeśli xjest 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 obiekcie x 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

r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

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…


Zasadniczo więc standard mówi, że C musi się zdarzyć przed D , ale kompilator uważa, że ​​nie można udowodnić, czy C lub D nastąpiło później, a dzięki regule as-if i tak je zmienia, prawda?
Fureeish

4
@ Fureeish No. C musi się zdarzyć przed D tak daleko, jak to tylko możliwe. Obserwacja z innego kontekstu może być niezgodna z tym poglądem.
Deduplicator

Nie ma „jakby reguły”
ciekawy

4
@curiousguy To twierdzenie wydaje się podobne do innych poprzednich ewangelizacji w C ++ .
Wyścigi lekkości na orbicie

1
@curiousguy Standardowo robi jedna etykieta na jego postanowień „Reguła jak gdyby” w przypisie: „Przepis ten jest czasem nazywany«jak gdyby»zasada” intro.execution
Caleth

1

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:

  • zapisz 1 do p1
  • załaduj p2 do temp
  • przechowywać 2 do p3

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:

  • zapisz 1 do p2 [sekwencjonowane przed załadowaniem p2]
  • [wykonaj powyższe]
  • zapisz 3 do p1 [sekwencyjnie po drugim zapisz do p1]

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:

  • ustaw temp na 1
  • zapisz temp. do p2
  • przechowywać 2 do p3
  • przechowywać 3 do 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.


1

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.


-7

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.

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.