Zamawianie modeli pamięci i widoczność?
Próbowałem szukać szczegółów na ten temat, czytałem nawet standard o muteksach i atomice... ale nadal nie mogłem zrozumieć gwarancji widoczności modelu pamięci C++11. Z tego, co rozumiem, bardzo ważną cechą mutex obok wzajemnego wykluczenia jest zapewnienie widoczności. Aka nie wystarczy, że tylko jeden wątek na raz zwiększa licznik, ważne jest, że wątek zwiększa licznik, który był przechowywany przez wątek, który był ostatnio za pomocą mutex(naprawdę Nie wiem dlaczego ludzie nie wspominają o tym więcej przy omawianiu muteksów, może miałem złych nauczycieli :)). Więc z tego co wiem, atomic nie wymusza natychmiastowej widoczności: (od osoby, która prowadzi wątek boost:: i zaimplementowała wątek c++11 i bibliotekę mutex):
Nie przeszkadza mi to. Ale problem polega na tym, że mam problem ze zrozumieniem czym są konstrukcje C++11 dotyczące atomic "globalne" i które zapewniają spójność tylko w przypadku zmiennych atomowych. W szczególności mam zrozumienie, które (jeśli w ogóle) z następujących kolejności pamięci gwarantują, że będzie płot pamięci przed i po załadowaniu i przechowuje: http://www.stdthread.co.uk/doc/headers/atomic/memory_order.htmlOgrodzenie z memory_order_seq_cst nie wymusza natychmiastowego widoczność do innych wątków (podobnie jak instrukcja MFENCE). Ograniczenia zamawiania pamięci C++0x są po prostu takie - - - zamawianie ograniczenia. operacje memory_order_seq_cst tworzą całkowity porządek, ale nie ma żadnych ograniczeń co do tego, czym jest to zamówienie, poza tym, że musi być uzgodnione przez wszystkie wątki i nie może naruszać innych zamówień ograniczenia. W szczególności wątki mogą nadal widzieć wartości "stare" przez jakiś czas, pod warunkiem, że widzą wartości w kolejności zgodnej z ograniczenia.
Z tego co mogę powiedzieć std::memory_order_seq_cst wstawia barierę mem, podczas gdy inne wymuszają tylko kolejność operacji w określonym miejscu pamięci.
Więc może ktoś to wyczyścić domyślam się, że wiele osób będzie robić straszne błędy używając std:: atomic, esp jeśli nie używają domyślnego (STD::memory_order_seq_cst memory ordering)
2. jeśli mam rację to znaczy, że druga linia jest redundand w tym kodzie:
atomicVar.store(42);
std::atomic_thread_fence(std::memory_order_seq_cst);
3. do std:: atomic_thread_fences mają te same wymagania co mutexy w tym sensie, że aby zapewnić spójność seq na nieatomowych var-ach, należy wykonać STD:: atomic_thread_fence(STD::memory_order_seq_cst);
przed załadowaniem i
std:: atomic_thread_fence (std:: memory_order_seq_cst);
po sklepach?
4. Is
{
regularSum+=atomicVar.load();
regularVar1++;
regularVar2++;
}
//...
{
regularVar1++;
regularVar2++;
atomicVar.store(74656);
}
Odpowiednik
std::mutex mtx;
{
std::unique_lock<std::mutex> ul(mtx);
sum+=nowRegularVar;
regularVar++;
regularVar2++;
}
//..
{
std::unique_lock<std::mutex> ul(mtx);
regularVar1++;
regularVar2++;
nowRegularVar=(74656);
}
Nie sądzę, ale chciałbym się upewnić.
Edytuj:
5.
Czy można prowadzić ogień?
Istnieją tylko dwa wątki.
atomic<int*> p=nullptr;
Pierwszy wątek pisze
{
nonatomic_p=(int*) malloc(16*1024*sizeof(int));
for(int i=0;i<16*1024;++i)
nonatomic_p[i]=42;
p=nonatomic;
}
Drugi wątek czyta
{
while (p==nullptr)
{
}
assert(p[1234]==42);//1234-random idx in array
}
2 answers
Jeśli lubisz zajmować się ogrodzeniami, to {[2] } jest równoważne a.load(memory_order_relaxed)
, a następnie atomic_thread_fence(memory_order_acquire)
. Podobnie, {[5] } jest równoważne wywołaniu do atomic_thread_fence(memory_order_release)
przed wywołaniem do a.store(x,memory_order_relaxed)
. memory_order_consume
jest szczególnym przypadkiem memory_order_acquire
, tylko dla danych zależnych . memory_order_seq_cst
jest specjalna i tworzy całkowity porządek we wszystkich operacjach memory_order_seq_cst
. W połączeniu z innymi jest to to samo, co nabycie dla ładunku, a zwolnienie dla sklepu. memory_order_acq_rel
jest dla operacji read-modify-write I jest odpowiednikiem acquire na części read I wydanie na stronie RMW.
Użycie ograniczeń porządkowania operacji atomowych może, ale nie musi, skutkować rzeczywistymi instrukcjami ogrodzenia, w zależności od architektury sprzętowej. W niektórych przypadkach kompilator wygeneruje lepszy kod, jeśli ograniczysz kolejność operacji atomowych, zamiast używać oddzielnego ogrodzenia.
Na x86 ładunki są zawsze nabywane, a sklepy są zawsze zwalniane. memory_order_seq_cst
wymaga silniejszego zamawiania z MFENCE
instrukcją lub LOCK
Instrukcja prefiksowa (jest tu wybór implementacji, czy sprawić, by Sklep miał mocniejsze zamawianie czy ładowanie). W związku z tym samodzielne ogrodzenia pozyskania i uwolnienia nie są ops, ale atomic_thread_fence(memory_order_seq_cst)
nie jest (ponownie wymaga MFENCE
lub LOCK
instrukcji ed).
Ważnym efektem ograniczeń porządkowania jest to, że zlecają one inne operacje .
std::atomic<bool> ready(false);
int i=0;
void thread_1()
{
i=42;
ready.store(true,memory_order_release);
}
void thread_2()
{
while(!ready.load(memory_order_acquire)) std::this_thread::yield();
assert(i==42);
}
thread_2
obraca się, aż odczytuje true
z ready
. Ponieważ sklep do ready
w thread_1
jest release, a load jest acquire następnie store synchronizuje-z load, a store do i
happens-before the load from i
in the assert, and the assert will not fire.
2) Druga linia w
atomicVar.store(42);
std::atomic_thread_fence(std::memory_order_seq_cst);
Jest rzeczywiście potencjalnie zbędny, ponieważ sklep do atomicVar
domyślnie używa memory_order_seq_cst
. Jeśli jednak istnieją inne operacje niememory_order_seq_cst
atomowe na tym wątku, ogrodzenie może mieć konsekwencje. Przykładowo, działałoby jako płot zwalniający dla kolejnego a.store(x,memory_order_relaxed)
.
atomic_thread_fence(memory_order_seq_cst)
. Nie ma wymogu, aby jakiekolwiek operacje atomowe były memory_order_seq_cst
, a uporządkowanie na zmiennych nieatomowych można osiągnąć bez, jak w powyższym przykładzie.
4) nie są one równoważne. Twój Fragment bez blokady mutex jest więc rasą danych i niezdefiniowany zachowanie.
5) nie twoje twierdzenie nie może strzelać. Przy domyślnej kolejności pamięci memory_order_seq_cst, store I load ze wskaźnika atomowego p
działają jak store I load w moim przykładzie powyżej, a Magazyny do elementów tablicy są gwarantowane-przed odczytaniem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-19 16:54:50
To naprawdę zależy od tego, co robisz i na jakiej platformie pracujesz. Silny model zamawiania pamięci na platformie takiej jak x86 stworzy inny zestaw wymagań dotyczących istnienia operacji ogrodzenia pamięci w porównaniu do słabszego modelu zamawiania na platformach takich jak IA64, PowerPC, ARM itp. Co do ... domyślnym parametremZ tego co mogę powiedzieć std::memory_order_seq_cst wstawia barierę mem, podczas gdy inne wymuszają tylko kolejność operacji w określonym miejscu pamięci.
std::memory_order_seq_cst
jest zapewnienie, że w zależności od platformy zostaną użyte odpowiednie instrukcje ogrodzenia pamięci. Na platformie takiej jak x86 nie ma potrzeby stosowania pełnej bariery pamięci, chyba że wykonujesz operację odczytu-modyfikacji-zapisu. W modelu pamięci x86 wszystkie ładunki mają semantykę load-acquire, a wszystkie sklepy mają semantykę store-release. Tak więc, w tych przypadkach std::memory_order_seq_cst
enum zasadniczo tworzy no-op, ponieważ model pamięci dla x86 już zapewnia, że tego typu operacje są spójne we wszystkich wątkach, dlatego nie ma instrukcji montażu, które implementują tego typu bariery pamięci częściowej. Tak więc ten sam warunek no-op byłby prawdziwy, jeśli jawnie ustawisz std::memory_order_release
lub std::memory_order_acquire
ustawienie na x86. Ponadto Wymaganie pełnej bariery pamięci w takich sytuacjach byłoby niepotrzebną przeszkodą w wydajności. Jak wspomniano, będzie on wymagany tylko do operacji read-modify-store.
Na innych platformach ze słabszymi modelami spójności pamięci, nie byłoby tak, dlatego użycie std::memory_order_seq_cst
wykorzystywałoby odpowiednie operacje ogrodzenia pamięci bez konieczności wyraźnego określania, czy użytkownik chciałby operacji Load-acquire, store-release czy full memory fence. Platformy te mają specjalne instrukcje maszynowe do egzekwowania takich umów spójności pamięci, a ustawienie std::memory_order_seq_cst
rozwiązałoby właściwy przypadek. Jeśli użytkownik chce konkretnie wywołać jedną z tych operacji, może za pomocą jawnego std::memory_order
typy enum, ale nie byłoby to konieczne ... kompilator wypracowałby poprawne ustawienia.
Zakładam, że wiele osób będzie robić straszne błędy używając std:: atomic, esp jeśli nie używają domyślnego (STD::memory_order_seq_cst memory ordering)
Tak, jeśli nie wiedzą, co robią i nie rozumieją, jakie typy semantyki bariery pamięci są wymagane w pewnych operacjach, wtedy będzie wiele błędów, jeśli spróbują wyraźnie określić rodzaj bariery pamięci i jest to niepoprawna, zwłaszcza na platformach, które nie pomogą im w złym zrozumieniu kolejności pamięci, ponieważ są słabsze w naturze.
Na koniec pamiętaj o swojej sytuacji # 4 dotyczącej mutex, że tutaj muszą się wydarzyć dwie różne rzeczy:
- kompilator nie może mieć możliwości zmiany kolejności operacji w sekcji mutex i critical (szczególnie w przypadku kompilatora optymalizującego)
- muszą istnieć wymagane ogrodzenia pamięci (w zależności od platformy), które utrzymują stan, w którym wszystkie sklepy są zakończone przed sekcją krytyczną i odczytem zmiennej mutex, a wszystkie sklepy są zakończone przed opuszczeniem sekcji krytycznej.
Ponieważ domyślnie, atomowe magazyny i ładunki są zaimplementowane za pomocą std::memory_order_seq_cst
, użycie atomiki zaimplementowałoby również odpowiednie mechanizmy spełniające warunki #1 i #2. To powiedziawszy, w Twoim pierwszym przykładzie w przypadku atomics obciążenie wymusi semantykę acquire-dla bloku, podczas gdy store wymusi semantykę release. Nie wymusiłoby to jednak żadnego konkretnego porządku wewnątrz "sekcji krytycznej" między tymi dwoma operacjami. W drugim przykładzie masz dwie różne sekcje z blokadami, z których każda posiada semantykę. Ponieważ w pewnym momencie trzeba by zwolnić blokady, które mają semantykę Wydania, to nie, dwa bloki kodu nie będą równoważne. W w pierwszym przykładzie utworzyłeś dużą "sekcję krytyczną" pomiędzy load I store (zakładając, że wszystko dzieje się w tym samym wątku). W drugim przykładzie masz dwie różne sekcje krytyczne.
P. S. znalazłem następujący PDF szczególnie pouczający, i może go również znajdziesz: http://www.nwcpp.org/Downloads/2008/Memory_Fences.pdf
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-19 14:57:49