Korzystanie z C / Pthreads: czy współdzielone zmienne muszą być lotne?

W języku programowania C i pthreads jako biblioteka wątków; czy zmienne / struktury współdzielone między wątkami muszą być zadeklarowane jako zmienne? Zakładając, że mogą być chronione przez zamek lub nie (być może bariery).

Czy standard POSIX pthread ma coś do powiedzenia na ten temat, czy ten kompilator jest zależny czy też nie?

Edit to add: Thanks for the great answers. Ale co jeśli używasz a nie zamków; co jeśli używasz barierek do przykład? Lub kod, który używa prymitywów, takich jak compare-and-swap , aby bezpośrednio i atomicznie zmodyfikować współdzieloną zmienną...

Author: fuad, 2008-09-17

13 answers

Myślę, że jedną z bardzo ważnych właściwości volatile jest to, że powoduje, że zmienna jest zapisywana do pamięci po zmodyfikowaniu i ponownie odczytywana z pamięci za każdym razem, gdy jest dostępna. Inne odpowiedzi tutaj mieszają lotne i synchronizacji, i jest jasne z niektórych innych odpowiedzi niż to, że lotne nie jest prymitywne synchronizacji (kredyt, gdzie kredyt jest należny).

Ale jeśli nie użyjesz volatile, kompilator może swobodnie buforować udostępnione dane w rejestrze przez Dowolny czas... jeśli chcesz, aby Twoje dane były napisany tak, aby mógł być zapisany do rzeczywistej pamięci, a nie tylko buforowany w rejestrze przez kompilator według własnego uznania, będziesz musiał oznaczyć go jako zmienny. Alternatywnie, jeśli uzyskasz dostęp do udostępnionych danych dopiero po opuszczeniu funkcji modyfikującej je, możesz być w porządku. Ale sugerowałbym, aby nie polegać na ślepym szczęściu, aby upewnić się, że wartości są zapisywane z powrotem z rejestrów do pamięci.

Szczególnie na maszynach bogatych w rejestry (tzn. nie x86), zmienne mogą żyć dość długo w rejestry, a dobry kompilator potrafi buforować nawet części struktur lub całe struktury w rejestrach. Więc powinieneś używać lotnych, ale dla wydajności, również kopiować wartości do zmiennych lokalnych do obliczeń, a następnie wykonać jawny odpis. Zasadniczo, korzystanie z volatile efektywnie oznacza wykonywanie trochę Load-store myślenia w kodzie C.

W każdym razie musisz użyć jakiegoś mechanizmu synchronizacji na poziomie systemu operacyjnego, aby utworzyć poprawny program.

Dla przykładu słabość lotnych, zobacz przykład algorytmu Deckera na http://jakob.engbloms.se/archives/65 , co udowadnia całkiem dobrze, że Lotny nie działa do synchronizacji.

 5
Author: jakobengblom2,
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
2009-01-05 19:43:02

Dopóki używasz blokad do kontrolowania dostępu do zmiennej, nie musisz jej używać. W rzeczywistości, jeśli umieszczasz zmienną zmienną, prawdopodobnie już się mylisz.

Https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/

 25
Author: Don Neufeld,
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
2016-01-10 08:01:21

Odpowiedź jest absolutnie, jednoznacznie, nie. Nie musisz używać "lotnych" oprócz odpowiednich prymitywów synchronizacji. Wszystko, co trzeba zrobić, robione jest przez tych prymitywów.

Użycie "lotnych" nie jest ani konieczne, ani wystarczające. Nie jest to konieczne, ponieważ odpowiednia synchronizacja prymitywów jest wystarczająca. To nie wystarczy, ponieważ wyłącza tylko niektóre optymalizacje, a nie wszystkie, które mogą cię ugryźć. Na przykład, nie gwarantuje ani atomiczność lub widoczność na innym procesorze.

Ale jeśli nie użyjesz volatile, kompilator może swobodnie buforować udostępnione dane w rejestrze przez Dowolny czas... jeśli chcesz, aby Twoje dane były zapisywane w przewidywalny sposób do rzeczywistej pamięci, a nie tylko buforowane w rejestrze przez kompilator według jego uznania, musisz oznaczyć je jako zmienne. Alternatywnie, jeśli uzyskasz dostęp do udostępnionych danych dopiero po opuszczeniu funkcji modyfikującej je, możesz być w porządku. Ale sugerowałbym nie poleganie na ślepym szczęściu, aby upewnić się, że wartości są zapisywane z rejestrów do pamięci.

Racja, ale nawet jeśli używasz volatile, procesor może swobodnie buforować udostępnione dane w buforze wysyłania zapisu przez Dowolny czas. Zestaw optymalizacji, które mogą cię ugryźć, nie jest dokładnie taki sam, jak zestaw optymalizacji, które "lotne" wyłącza. Więc jeśli używasz 'volatile', tyjesteś polegasz na ślepym szczęściu.

Z drugiej strony, jeśli używasz primitives sychronizacji dzięki zdefiniowanej wielowątkowej semantyce masz gwarancję, że wszystko będzie działać. Jako plus, nie bierzesz wielkiego hitu wydajności "volatile". Więc dlaczego nie robić rzeczy w ten sposób?

 9
Author: Justin M. Keyes,
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
2018-01-25 15:02:41

Istnieje powszechne przekonanie, że słowo kluczowe volatile jest dobre dla programowania wielowątkowego.

Hans Boehm zwraca uwagę , że istnieją tylko trzy przenośne zastosowania lotnych:

  • volatile mogą być używane do oznaczania zmiennych lokalnych w tym samym zakresie co setjmp, których wartość powinna być zachowana w longjmp. Nie jest jasne, jaka część takich zastosowań zostanie spowolniona, ponieważ ograniczenia atomiczności i porządkowania nie mają wpływu, jeśli nie ma sposobu na Udostępnij daną zmienną lokalną. (Nie jest nawet jasne, jaki ułamek takich zastosowań zostałby spowolniony przez wymaganie zachowania wszystkich zmiennych w longjmp, ale jest to osobna sprawa i nie jest tu brane pod uwagę.)
  • volatile może być używany, gdy zmienne mogą być "modyfikowane zewnętrznie" , ale modyfikacja w rzeczywistości jest uruchamiana synchronicznie przez sam wątek, np. ponieważ pamięć bazowa jest mapowana w wielu miejscach.
  • a lotne sigatomic_t może być używany do komunikacji z obsługą sygnału w tym samym wątku, w sposób ograniczony. Można rozważyć osłabienie wymagań dla przypadku sigatomic_t, ale wydaje się to raczej sprzeczne z intuicją.

Jeśli jesteś wielowątkowy ze względu na szybkość, spowolnienie kodu zdecydowanie nie jest tym, czego chcesz. W przypadku programowania wielowątkowego istnieją dwie kluczowe kwestie, które często błędnie adres:

  • atomicity
  • spójność pamięci , tzn. kolejność operacji wątku widziana przez inny wątek.
Najpierw zajmiemy się (1). Volatile nie gwarantuje atomowych odczytów ani zapisów. Na przykład zmienny odczyt lub zapis 129-bitowej struktury nie będzie atomowy na większości nowoczesnych urządzeń. Zmienny odczyt lub zapis 32-bitowego int jest atomowy na większości nowoczesnych urządzeń, ale zmienny nie ma z tym nic wspólnego. Informatyka prawdopodobnie byłby atomowy bez lotnych. Atomiczność jest w kaprysie kompilatora. W standardach C lub c++ nie ma nic, co by mówiło, że musi być atomowe.

Teraz rozważmy problem (2). Czasami Programiści myślą o volatile jako o wyłączeniu optymalizacji volatile accesses. To w dużej mierze prawda w praktyce. Ale to tylko niestabilne dostępy, a nie nieulotne. Rozważ ten fragment:

 volatile int Ready;       

    int Message[100];      

    void foo( int i ) {      

        Message[i/10] = 42;      

        Ready = 1;      

    }

Próbuje zrobić coś bardzo rozsądnego w wielowątkowym programowanie: napisz wiadomość, a następnie wyślij ją do innego wątku. Drugi wątek będzie czekał, aż Ready stanie się niezerowe, a następnie odczyta wiadomość. Spróbuj skompilować to za pomocą "gcc-O2-s" używając gcc 4.0 lub icc. Oba będą najpierw gotowe, więc można je nakładać na obliczenia i/10. Zmiana kolejności nie jest błędem kompilatora. To agresywny optymalizator wykonujący swoją pracę.

Można by pomyśleć, że rozwiązaniem jest oznaczanie wszystkich odniesień do pamięci. To po prostu głupie. Jako wcześniejsze cytaty mówią, że po prostu spowolni to Twój kod. Najgorsze jest to, że może nie rozwiązać problemu. Nawet jeśli kompilator nie zmieni kolejności odniesień, sprzęt może. W tym przykładzie sprzęt x86 nie zmieni jego kolejności. Podobnie jak procesor Itanium(TM), ponieważ Kompilatory Itanium wstawiają ogrodzenia pamięci dla zmiennych magazynów. Sprytne rozszerzenie Itanium. Ale chipy takie jak moc (TM)zmieni kolejność. To, czego naprawdę potrzebujesz do zamówienia, to ogrodzenia pamięci , zwane również pamięcią bariery . Ogrodzenie pamięci zapobiega ponownemu uporządkowaniu operacji pamięci w poprzek ogrodzenia lub w niektórych przypadkach zapobiega ponownemu uporządkowaniu w jednym kierunku.Lotność nie ma nic wspólnego z płotami pamięci.

Jakie jest rozwiązanie dla programowania wielowątkowego? Użyj biblioteki lub rozszerzenia językowego, które implementuje semantykę atomic i fence. Gdy zostaną użyte zgodnie z przeznaczeniem, operacje w bibliotece wstawią odpowiednie ogrodzenia. Niektóre przykłady:

  • wątki POSIX
  • Windows(TM) wątki
  • OpenMP
  • TBB

Na podstawie artykułu Archa Robisona (Intel)

 3
Author: IOException,
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-11-14 10:21:12

Z mojego doświadczenia wynika, że nie; musisz po prostu odpowiednio mutex samodzielnie pisząc do tych wartości, lub tak skonstruować swój program, aby wątki zatrzymały się, zanim będą musiały uzyskać dostęp do danych, które zależą od działań innego wątku. Mój projekt, x264, używa tej metody; wątki dzielą ogromną ilość danych, ale zdecydowana większość z nich nie potrzebuje mutexów, ponieważ albo tylko do odczytu, albo wątek będzie czekał na dane, aby stały się dostępne i sfinalizowane, zanim będzie musiał uzyskać dostęp to.

Teraz, jeśli masz wiele wątków, które są mocno przeplatane w swoich operacjach (zależą one od wydajności innych na bardzo drobnoziarnistym poziomie), może to być o wiele trudniejsze-w rzeczywistości, w takim przypadku rozważyłbym ponowne przeanalizowanie modelu wątku, aby zobaczyć, czy można to zrobić bardziej czysto z większym oddzieleniem między wątkami.

 2
Author: Dark Shikari,
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
2008-09-16 23:03:43

Nie.

Volatile jest wymagany tylko podczas odczytu lokalizacji pamięci, która może zmieniać się niezależnie od poleceń odczytu/zapisu procesora. W sytuacji threadingu procesor ma pełną kontrolę nad odczytem/zapisem do pamięci dla każdego wątku, dlatego kompilator może założyć, że pamięć jest spójna i optymalizuje instrukcje procesora w celu zmniejszenia niepotrzebnego dostępu do pamięci.

Podstawowym zastosowaniem dla {[1] } jest dostęp do mapowanych w pamięci We/Wy. w tym przypadku urządzenie bazowe może zmienić wartość lokalizacji pamięci niezależnie od procesora. Jeśli nie użyjesz volatile pod tym warunkiem, procesor może użyć wcześniej buforowanej wartości pamięci zamiast odczytu nowo zaktualizowanej wartości.

 2
Author: cmcginty,
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
2016-07-06 10:17:12

Volatile przydałby się tylko wtedy, gdy nie potrzebujesz żadnych opóźnień między tym, kiedy jeden wątek coś pisze, a inny wątek to czyta. Bez jakiegoś zamka nie masz pojęcia o Kiedy inny wątek napisał dane, tylko że jest to najnowsza możliwa wartość.

Dla prostych wartości (int i float w różnych rozmiarach) mutex może być przesadą, jeśli nie potrzebujesz jawnego punktu synchronizacji. Jeśli nie używasz mutex lub lock jakiegoś rodzaju, powinieneś zadeklarować zmienna lotna. Jeśli używasz mutexu, wszystko gotowe.

Dla skomplikowanych typów, musisz użyć mutex. Operacje na nich nie są atomowe, więc można przeczytać pół-zmienioną wersję bez mutex.

 0
Author: Branan,
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
2008-09-16 23:10:14

Volatile oznacza, że musimy przejść do pamięci, aby uzyskać lub ustawić tę wartość. Jeśli nie ustawisz volatile, skompilowany kod może przechowywać dane w rejestrze przez długi czas.

Oznacza to, że należy oznaczyć zmienne współdzielone między wątkami jako zmienne, aby nie mieć sytuacji, w których jeden wątek zaczyna modyfikować wartość, ale nie zapisuje jej wyniku, zanim pojawi się drugi wątek i spróbuje odczytać wartość.

Volatile jest kompilatorem, który wyłącza pewne optymalizacje. Skład wyjściowy kompilatora może być bezpieczny bez niego, ale zawsze należy go używać do współdzielonych wartości.

Jest to szczególnie ważne, jeśli nie używasz drogich obiektów synchronizacji wątków dostarczanych przez Twój system - możesz na przykład mieć strukturę danych, w której możesz zachować jej ważność z serią atomowych zmian. Wiele stosów, które nie przydzielają pamięci, jest przykładami takich struktur danych, ponieważ można wtedy dodać wartość do stosu przesuń wskaźnik końcowy lub usuń wartość ze stosu po przesunięciu wskaźnika końcowego. Podczas wdrażania takiej struktury, lotność staje się kluczowa, aby upewnić się, że instrukcje atomowe są rzeczywiście atomowe.

 0
Author: Tom Leys,
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
2008-09-16 23:17:17

Powodem jest to, że semantyka języka C opiera się na jednowątkowej maszynie abstrakcyjnej. A kompilator ma prawo przekształcić program tak długo, jak 'obserwowalne zachowania' programu na maszynie abstrakcyjnej pozostaną niezmienione. Może łączyć sąsiadujące lub nakładające się dostępy do pamięci, wielokrotnie ponawiać dostęp do pamięci (na przykład podczas rozlewania rejestru) lub po prostu odrzucać dostęp do pamięci, jeśli myśli o zachowaniach programu, gdy jest wykonywany w pojedynczy wątek , nie zmienia się. Dlatego, jak można podejrzewać, zachowania zmieniają się, jeśli program rzeczywiście ma być wykonywany w wielowątkowy sposób.

Jak zauważył Paul Mckenney w słynnym dokumencie jądra Linuksa :

It _must_not_ należy założyć, że kompilator zrobi to, co chcesz z odwołaniami do pamięci, które nie są chronione przez READ_ONCE () i WRITE_ONCE (). Bez nich kompilator ma swoje prawa na czy wszelkiego rodzaju" twórczych " przekształceń, które są objęte sekcja bariery kompilatora.

READ_ONCE() i WRITE_ONCE() są zdefiniowane jako lotne odlewy na zmiennych odniesienia. Tak więc:

int y;
int x = READ_ONCE(y);

Jest równoważne:

int y;
int x = *(volatile int *)&y;

Więc, jeśli nie dokonasz "niestabilnego" dostępu, nie masz pewności, że dostęp nastąpi dokładnie raz , bez względu na mechanizm synchronizacji, którego używasz. Wywołanie funkcji zewnętrznej (pthread_mutex_lock dla przykład) może wymusić na kompilatorze dostęp do pamięci zmiennych globalnych. Ale dzieje się to tylko wtedy, gdy kompilator nie może dowiedzieć się, czy zewnętrzna funkcja zmienia te zmienne globalne, czy nie. Nowoczesne Kompilatory wykorzystujące zaawansowaną analizę między procedurami i optymalizację czasu połączenia sprawiają, że ta sztuczka jest po prostu bezużyteczna.

W podsumowaniu należy zaznaczyć zmienne współdzielone przez wiele wątków lub uzyskać do nich dostęp za pomocą odlewów lotnych.


Jako Paul McKenney również

Widziałem błysk w ich oczach, gdy omawiali techniki optymalizacji, o których nie chciałbyś, aby twoje dzieci wiedziały!


Ale zobacz co się stanie z C11 / C++11 .

 0
Author: Hatrick,
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
2017-10-28 09:00:29

Nie rozumiem. W jaki sposób synchronizacja podstawowych elementów zmusza kompilator do przeładowania wartości zmiennej? Dlaczego po prostu nie użyje najnowszej kopii, którą już posiada?

Volatile oznacza, że zmienna jest aktualizowana poza zakresem kodu, a więc kompilator nie może założyć, że zna jej bieżącą wartość. Nawet bariery pamięci są bezużyteczne, jak kompilator, który jest nieświadomy barier pamięci (prawda?), może nadal używać wartości buforowanej.

 -1
Author: jjj,
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
2010-09-15 23:54:27

Niektórzy oczywiście zakładają, że kompilator traktuje wywołania synchronizacji jako bariery pamięci. "Casey" zakłada, że jest dokładnie jeden procesor.

Jeśli Sync primitives są funkcjami zewnętrznymi, a symbole są widoczne poza jednostką kompilacji (nazwy globalne, eksportowany wskaźnik, eksportowana funkcja, która może je modyfikować), to kompilator potraktuje je - lub jakiekolwiek inne wywołanie funkcji zewnętrznej-jako ogrodzenie pamięci w odniesieniu do wszystkich widocznych na zewnątrz obiektów.

W Przeciwnym Razie, jesteś zdany na siebie. A volatile może być najlepszym dostępnym narzędziem do tworzenia poprawnego, szybkiego kodu przez kompilator. Generalnie jednak nie będzie przenośny, kiedy potrzebujesz volatile, a to, co faktycznie dla Ciebie robi, zależy w dużej mierze od systemu i kompilatora.

 -1
Author: Stephen Nuchia,
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
2010-09-22 15:35:56

Nie.

Po pierwsze, {[0] } nie jest konieczne. Istnieje wiele innych operacji, które zapewniają gwarantowaną wielowątkową semantykę, która nie używa volatile. Należą do nich operacje atomowe, muteksy i tak dalej.

Po Drugie, {[0] } nie wystarcza. Standard C nie zapewnia żadnych gwarancji dotyczących zachowania wielowątkowego dla zmiennych zadeklarowanych volatile.

Więc nie będąc ani koniecznym, ani wystarczającym, nie ma sensu go używać.

Jeden wyjątek byłby szczególny platformy (takie jak Visual Studio), gdzie ma udokumentowaną wielowątkową semantykę.

 -1
Author: David Schwartz,
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
2016-04-13 19:52:14

Zmienne współdzielone między wątkami powinny być zadeklarowane jako "lotne". To mówi kompilatora, że gdy jeden wątek zapisuje do takich zmiennych, zapis powinien być do pamięci (w przeciwieństwie do rejestru).

 -2
Author: Adam Soffer,
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
2010-11-02 03:35:29