C ++ std :: memory_order_relaxed zamieszanie

Oct 12 2020

Czytałem ten artykuł z GCC Wiki o barierach pamięci C ++ (i jego niesamowitych).
To było całkiem proste, dopóki nie doszedłem do tego punktu:

Odwrotnym podejściem jest std :: memory_order_relaxed. Model ten pozwala na znacznie mniejszą synchronizację dzięki usunięciu ograniczeń „dzieje się przed”. W przypadku tych typów operacji atomowych można również wykonywać różne optymalizacje, takie jak usuwanie martwych zasobów i udostępnianie. Więc we wcześniejszym przykładzie:

-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)

-Thread 2-
if (x.load (memory_order_relaxed) == 10)
  {
    assert (y.load(memory_order_relaxed) == 20) /* assert A */
    y.store (10, memory_order_relaxed)
  }

-Thread 3-
if (y.load (memory_order_relaxed) == 10)
  assert (x.load(memory_order_relaxed) == 10) /* assert B */

Ponieważ wątki nie muszą być synchronizowane w całym systemie, każda asercja w tym przykładzie może w rzeczywistości FAIL.

Ok, to też jest proste, kontynuujmy.

-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)

-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)

Twierdzenie nie może zawieść. Gdy wątek 2 zobaczy magazyn 2, nie będzie mógł już widzieć wartości 1. Zapobiega to łączeniu rozluźnionych obciążeń jednej zmiennej z rozluźnionymi obciążeniami innego odniesienia, które może być aliasem.

To jest to, co mnie zdezorientowało, dlaczego y nie może załadować wartości 2, a z załadować wartość 1 (i spowodować niepowodzenie asercji), ponieważ kolejność nie jest zsynchronizowana w wątku 1?

Odpowiedzi

4 NicolBolas Oct 12 2020 at 12:12

Porządkowanie rozluźnione odnosi się do kolejności operacji w odniesieniu do innych dostępów do pamięci , a nie do porządkowania względem atomowej, która jest modyfikowana relaksacyjnie. W twoim pierwszym przypadku fakt, że widzisz 10 w x, nie ma żadnego znaczenia w odniesieniu do wartości y. I wzajemnie.

Ale twój drugi przypadek jest inny, ponieważ wpływa na ten sam obiekt atomowy.

[intro.races] / 10 mówi nam, że w ramach wątku, jeśli jedna operacja jest sekwencjonowana przed drugą, to ta operacja „ma miejsce przed” drugą. Natomiast [intro.races] / 14-17 przedstawiają następujące zachowanie w odniesieniu do atomów :

Cztery poprzednie wymagania dotyczące spójności skutecznie uniemożliwiają kompilatorowi zmianę kolejności operacji atomowych na pojedynczy obiekt, nawet jeśli obie operacje są obciążeniami zrelaksowanymi.

I to właśnie masz tutaj. Wszystkie modyfikacje dotyczą tego samego obiektu, więc muszą następować w jakiejś kolejności. Nawet jeśli tej kolejności nie można dokładnie określić, kolejność musi uwzględniać relacje „zdarza się przed” w kodzie.

Dwie operacje wątku 1 są uporządkowane według relacji „dzieje się przed”. Same operacje wątku 2 są uporządkowane według relacji „zdarza się przed”.

Ponieważ wszystkie działają na tym samym obiekcie atomowym, jeśli yotrzyma wartość 2, to musiało to być „wydarzyło się po” xustawionym na 2. Zatem kolejność dostępu dla xmusiała być „x = 1, x = 2, przeczytaj x ”. A ponieważ ostatni odczyt xnastępuje po pierwszym x, otrzymana wartość nie może wynosić 1.