2016-04-08 14 views
13

Uruchomiłem wątek, który działa do momentu ustawienia flagi.Czy std :: atomic powinien być lotny?

std::atomic<bool> stop(false); 

void f() { 
    while(!stop.load(std::memory_order_{relaxed,acquire})) { 
    do_the_job(); 
    } 
} 

Zastanawiam się, czy kompilator może rozwinąć pętlę tak (nie chcę, by to się stało).

void f() { 
    while(!stop.load(std::memory_order_{relaxed,acquire})) { 
    do_the_job(); 
    do_the_job(); 
    do_the_job(); 
    do_the_job(); 
    ... // unroll as many as the compiler wants 
    } 
} 

Mówi się, że zmienność i atomowość są ortogonalne, ale jestem nieco zdezorientowany. Czy kompilator może swobodnie buforować wartość zmiennej atomowej i rozwinąć pętlę? Jeśli kompilator może rozwinąć pętlę, to myślę, że muszę umieścić flagę na volatile i chcę mieć pewność.

Czy powinienem wstawić volatile?


Przykro mi z powodu niejednoznaczności. Ja (domyślam się, że ja) rozumiem, co to jest zmiana kolejności i co znaczy memory_order_* i jestem pewien, że w pełni rozumiem, co to jest.

Myślę, że pętla while() może zostać przekształcona jako nieskończone if takie instrukcje.

void f() { 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    do_the_job(); 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    do_the_job(); 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    do_the_job(); 
    ... 
} 

Ponieważ przyjmować zleceń pamięci nie uniemożliwiają zsekwencjonowany-przed operacje z przemieszczane obok ładunku atomowego, myślę, że to może być uporządkowane, czy to bez lotny.

void f() { 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 
    ... 
    do_the_job(); 
    do_the_job(); 
    do_the_job(); 
    ... 
} 

Jeśli atomowa nie oznacza lotny, to myślę, że kod może być nawet tak przekształcone w najgorszym przypadku.

void f() { 
    if(stop.load(std::memory_order_{relaxed,acquire})) return; 

    while(true) { 
    do_the_job(); 
    } 
} 

Nigdy nie będzie tak szalony realizacji, ale myślę, że to jeszcze możliwe sytuacja. Myślę, że jedynym sposobem, aby temu zapobiec, jest umieszczenie volatile na zmiennej atomowej i pytam o to.

Jest wiele domysłów, które zrobiłem, proszę powiedz mi, czy coś jest nie tak.

+0

Nie sądzę. Ostatnio dużo oglądałem dla 'std :: atomic', ale nikt nie powiedział, że tak powinno być. Chyba w klasie jest "zmienna" zmienna gdzieś. – Nick

+2

Możliwy duplikat [Współbieżność: Atomowa i lotna w modelu pamięci C++ 11] (http://stackoverflow.com/questions/8819095/concurrency-atomic-and-volatile-in-c11-memory-model) –

+1

Nie, to nie powinno być niestabilne. –

Odpowiedz

7

Czy kompilator może za darmo buforować wartość zmiennej atomowej i rozwinąć pętlę?

Kompilator nie może buforować wartości zmiennej atomowej.

Jednak, ponieważ używasz std::memory_order_relaxed, oznacza to, że kompilator może dowolnie zmieniać kolejność ładowań i zapisów od/do tej zmiennej atomowej w odniesieniu do innych ładunków i zapasów.

Należy również zauważyć, że wywołanie funkcji, której definicja nie jest dostępna w tej jednostce tłumaczeniowej, jest barierą pamięci kompilatora. Oznacza to, że wywołanie nie może zostać zmienione w odniesieniu do otaczających ładunków i zapasów oraz że wszystkie nielokalne zmienne muszą zostać ponownie załadowane z pamięci po wywołaniu, tak jakby wszystkie były oznaczone jako lotne. (Zmienne lokalne, których adres nie został przekazany w innym miejscu, nie zostaną załadowane ponownie).

transformacja kodu chcesz uniknąć nie byłaby ważna przemiana ponieważ naruszałoby C++ modelu pamięci: w pierwszym przypadku masz jeden ładunek zmiennej atomowej następuje wywołanie do_the_job, w po drugie, masz wiele połączeń.Obserwowane zachowanie przekształconego kodu może być inne.


i notatki z std::memory_order:

związku z lotnym

ciągu wątku wykonania, dostęp (odczyt i zapis) do wszystkich lotnych obiektach są gwarancją nie zostać zreorganizowane względem siebie, ale kolejność ta nie może być przestrzegana przez inny wątek, ponieważ lotny dostęp nie ustanawia synchronizacji między wątkami.

Ponadto lotne dostępy nie są niepodzielne (równoległe do odczytu i zapisu jest wyścig danych) i nie zamówić pamięć (pamięć nieulotna dostęp może być dowolnie kolejność wokół lotnej dostępu).

Ten bit pamięć nieulotna dostęp może być dowolnie kolejność wokół lotnej dostępu jest prawdziwe dla spokojnych atomistyki, jak również, ponieważ obciążenie zrelaksowany i sklepy mogą być kolejność w odniesieniu do innych obciążeń i sklepach.

Innymi słowy, zdobycie atomowej z volatile nie zmieniłoby zachowania twojego kodu.


Niezależnie, 11 zmienne atomowe C++ nie muszą być oznakowane volatile hasła.


Oto przykład jak g ++ - 5.2 honoruje zmienne atomowe. Następujące funkcje:

__attribute__((noinline)) int f(std::atomic<int>& a) { 
    return a.load(std::memory_order_relaxed); 
} 

__attribute__((noinline)) int g(std::atomic<int>& a) { 
    static_cast<void>(a.load(std::memory_order_relaxed)); 
    static_cast<void>(a.load(std::memory_order_relaxed)); 
    static_cast<void>(a.load(std::memory_order_relaxed)); 
    return a.load(std::memory_order_relaxed); 
} 

__attribute__((noinline)) int h(std::atomic<int>& a) { 
    while(a.load(std::memory_order_relaxed)) 
     ; 
    return 0; 
} 

skompilowany z g++ -o- -Wall -Wextra -S -march=native -O3 -pthread -std=gnu++11 test.cc | c++filt > test.S powstawanie następującego zestawu:

f(std::atomic<int>&): 
    movl (%rdi), %eax 
    ret 

g(std::atomic<int>&): 
    movl (%rdi), %eax 
    movl (%rdi), %eax 
    movl (%rdi), %eax 
    movl (%rdi), %eax 
    ret 

h(std::atomic<int>&): 
.L4: 
    movl (%rdi), %eax 
    testl %eax, %eax 
    jne .L4 
    ret 
+0

Nie sądzę, że możemy założyć, że funkcja będzie barierą dla kompilatora, ponieważ istnieje bestia o nazwie LTO. Czy masz na myśli to, że nawet dwie kolejne operacje ładunków atomowych na tej samej zmiennej nie mogą zostać przekształcone w pojedyncze obciążenie? – kukyakya

+0

@kukyakya Model pamięci sugeruje, że zmienna atomowa może zostać zmieniona przez inny wątek, dlatego obciążenie nie może zostać usunięte. Zniesienie obciążenia zmiennej atomowej spowodowałoby, że zapisy do zmiennej atomowej byłyby niewidoczne dla innych wątków, co naruszałoby model pamięci, który gwarantuje widoczność magazynów na zmienne atomowe. –

+0

"który gwarantuje widoczność magazynów na zmienne atomowe" Nie ma gwarancji, że sklep stanie się widoczny dla innych ładunków w dowolnym skończonym czasie; najlepsze, co mamy, to: "Wdrożenia powinny sprawić, że magazyny atomowe będą widoczne w ładunkach atomowych w rozsądnym czasie", co jest zachętą normatywną ("powinno"), a nie wymogiem. –

2

Jeśli do_the_job() nie zmienia stop, to nie ma znaczenia, jeżeli kompilator może rozwinąć się pętlę, czy nie.

std::memory_order_relaxed po prostu zapewnia, że ​​każda operacja jest atomowa, ale nie zapobiega ponownym dostępom. Oznacza to, że jeśli inny wątek ustawia stop na true, pętla może kontynuować wykonywanie kilka razy, ponieważ dostępy mogą zostać zmienione. Tak więc jest to ta sama sytuacja, co w przypadku rozwiniętej pętli: do_the_job() może zostać wykonana kilka razy po tym, jak inny wątek ustawił stop na true.

Więc nie, nie używaj volatile, używaj std::memory_order_acquire i std::memory_order_release.

+0

Dostałem twój punkt. Ponieważ nie ma gwarancji, że operacja ładowania otrzyma ostatnią wartość, nie ma sensu ograniczać liczby wywołań funkcji. A co z przypadkiem, który dodałem? – kukyakya

+0

Trudno mi o tym myśleć, nie wiedząc, co robi 'do_the_job()' i co robi wątek ustawiający 'stop'. Z pewnością potrzebna jest większa synchronizacja między tymi dwoma, jeśli mam dostęp do wspólnych danych. Czy możesz zamieścić bardziej szczegółowy przykład? – alain

Powiązane problemy