Mam listę, z której chcę, aby różne wątki pobierały elementy. Aby uniknąć zablokowania muteksu strzegącego listy, gdy jest pusta, sprawdzam empty()
przed zablokowaniem.
W porządku, jeśli wezwanie do list::empty()
jest nieprawidłowe w 100% przypadków. Chcę tylko uniknąć awarii lub zakłóceń jednoczesnych list::push()
i list::pop()
połączeń.
Czy mogę bezpiecznie założyć, że VC ++ i Gnu GCC tylko czasami się empty()
mylą i nic gorszego?
if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
mutex.lock();
if(list.empty() == false){ // check again while locked to be certain
element = list.back();
list.pop_back();
}
mutex.unlock();
}
c++
multithreading
optimization
race-condition
stdlist
Anne Quinn
źródło
źródło
std::list::size
gwarantuje stałą złożoność czasu, co w zasadzie oznacza, że rozmiar (liczba węzłów) musi być przechowywany w osobnej zmiennej; nazwijmy tosize_
.std::list::empty
wtedy prawdopodobnie zwróci coś jakosize_ == 0
, a jednoczesne odczytywanie i zapisywaniesize_
spowoduje wyścig danych, a zatem UB.Odpowiedzi:
Nie, nie jest dobrze. Jeśli sprawdzisz, czy lista jest pusta poza jakimś mechanizmem synchronizacji (blokowanie muteksu), oznacza to, że masz wyścig danych. Wyścig danych oznacza, że masz niezdefiniowane zachowanie. Niezdefiniowane zachowanie oznacza, że nie możemy dłużej myśleć o programie, a wszelkie otrzymywane dane wyjściowe są „poprawne”.
Jeśli cenisz swoje zdrowie psychiczne, weźmiesz hit wydajności i zablokujesz muteks przed sprawdzeniem. To powiedziawszy, lista może nie być dla ciebie odpowiednim pojemnikiem. Jeśli możesz nam powiedzieć dokładnie, co robisz z tym, możemy zaproponować lepszy pojemnik.
źródło
list::empty()
to czynność czytania, która nie ma nic wspólnego zrace-condition
read-write
lubwrite-write
. Jeśli nie wierz mi, daj sekcji STANDARDOWE na wyścigach danych odczytuIstnieje odczyt i zapis (najprawdopodobniej do
size
członkastd::list
, jeśli założymy, że tak się nazywa), które nie są zsynchronizowane ze sobą w odczynniku . Wyobraź sobie, że jeden wątek wywołujeempty()
(w twoim zewnętrznymif()
), podczas gdy drugi wątek wszedł w wewnętrznyif()
i wykonuje siępop_back()
. Czytasz wtedy zmienną, która prawdopodobnie jest modyfikowana. To jest niezdefiniowane zachowanie.źródło
Jako przykład tego, jak coś może pójść nie tak:
Wystarczająco inteligentny kompilator może zobaczyć, że
mutex.lock()
nie może zmienićlist.empty()
wartości zwracanej, a tym samymif
całkowicie pomija kontrolę wewnętrzną , ostatecznie prowadząc dopop_back
listy na liście, której ostatni element został usunięty po pierwszejif
.Dlaczego to może zrobić? Synchronizacja nie jest możliwa
list.empty()
, więc gdyby została zmieniona jednocześnie, stanowiłoby to wyścig danych. Standard mówi, że programy nie będą miały wyścigów danych, więc kompilator weźmie to za pewnik (w przeciwnym razie może nie przeprowadzić prawie żadnych optymalizacji). Dlatego może przyjąć perspektywę jednowątkową na niezsynchronizowanymlist.empty()
i stwierdzić, że musi pozostać stała.To tylko jedna z wielu optymalizacji (lub zachowań sprzętowych), które mogą uszkodzić Twój kod.
źródło
a.load()+a.load()
...a.load()*2
jest oczywista?a.load(rel)+b.load(rel)-a.load(rel)
Zresztą nawet nie jest zoptymalizowany. Nic nie jest. Dlaczego oczekujesz, że blokady (które zasadniczo mają spójność sekwencji) będą bardziej zoptymalizowane?a.load() + a.load()
do2 * a.load()
. Jeśli chcesz dowiedzieć się więcej, zadaj pytanie na ten temat.