Volatile vs. Interlocked vs. lock
Załóżmy, że klasa ma pole public int counter
, do którego dostęp ma wiele wątków. To int
jest tylko zwiększane lub zmniejszane.
Aby zwiększyć to pole, które podejście powinno być używane i dlaczego?
-
lock(this.locker) this.counter++;
, -
Interlocked.Increment(ref this.counter);
, - Zmień modyfikator dostępu z
counter
napublic volatile
.
Teraz, kiedy odkryłem volatile
, usuwałem wiele lock
stwierdzeń i używania Interlocked
. Ale czy jest jakiś powód, by tego nie robić?
9 answers
Worst (won ' t actually work)
Zmień modyfikator dostępu z
counter
napublic volatile
Jak inni ludzie wspominali, to samo z siebie nie jest w ogóle bezpieczne. Punktem volatile
jest to, że wiele wątków działających na wielu procesorach może i będzie buforować dane i ponownie zamawiać instrukcje.
Jeśli jest Nie volatile
, PROCESOR a zwiększa wartość, wtedy procesor B może nie widzieć tej wartości do pewnego czasu później, co może spowodować problemy.
Jeśli jest to volatile
, zapewnia to, że dwa procesory widzą te same dane w tym samym czasie. Nie powstrzymuje ich to w ogóle przed przeplataniem operacji odczytu i zapisu, co jest problemem, którego próbujesz uniknąć.
Drugi Najlepszy:
lock(this.locker) this.counter++
;
Jest to bezpieczne do zrobienia (pod warunkiem, że pamiętasz lock
wszędzie, gdzie masz dostęp this.counter
). Uniemożliwia to innym wątkom wykonywanie innego kodu, który jest strzeżony przez locker
.
Używanie zamków ponadto zapobiega problemom z ponownym porządkowaniem wielu procesorów, jak powyżej, co jest świetne.
Problem polega na tym, że blokowanie jest powolne, a jeśli ponownie użyjesz locker
w innym miejscu, które nie jest naprawdę powiązane, możesz zakończyć blokowanie innych wątków bez powodu.
Najlepsze
Interlocked.Increment(ref this.counter);
Jest to bezpieczne, ponieważ skutecznie wykonuje odczyt, przyrost i zapis w "jednym uderzeniu", którego nie można przerwać. Z tego powodu nie wpłynie to na żaden inny kod, a ty nie trzeba pamiętać, aby zamknąć gdzie indziej albo. Jest również bardzo szybki(jak mówi MSDN, na nowoczesnych procesorach jest to często dosłownie pojedyncza Instrukcja procesora).
Nie jestem do końca pewien jednak, czy to się wokół innych procesorów zmiany kolejności rzeczy, lub jeśli trzeba również połączyć lotne z przyrostem.
InterlockedNotes:
- metody blokowane są jednocześnie bezpieczne dla dowolnej liczby rdzeni lub procesorów.
- metody blokowane stosują pełne ogrodzenie wokół instrukcje wykonują, więc zmiana kolejności nie ma miejsca.
- metody Interlockednie potrzebują lub nawet nie wspierają dostępu do pola lotnego , ponieważ Lotny jest umieszczony w połowie ogrodzenia wokół operacji na danym polu, a interlocked używa pełnego ogrodzenia.
Przypis: do czego właściwie służy lotność.
Ponieważ volatile
nie zapobiega tego typu problemom z wielowątkowością, do czego to służy? Dobrym przykładem jest stwierdzenie, że masz dwa wątki, jeden, który zawsze zapisuje do zmiennej (powiedzmy queueLength
) i zawsze odczytuje z tej samej zmiennej.
Jeśli queueLength
nie jest zmienna, wątek A może pisać pięć razy, ale wątek B może postrzegać te zapisy jako opóźnione (lub nawet potencjalnie w złej kolejności).
Rozwiązaniem byłoby zablokowanie, ale można również użyć volatile w tej sytuacji. Zapewni to, że wątek B będzie zawsze widział najbardziej aktualną rzecz, którą napisał wątek A. Zauważ jednak, że ta logikatylko działa jeśli masz pisarzy, którzy nigdy nie czytają i czytelników, którzy nigdy nie piszą, i jeśli to, co piszesz, jest wartością atomową. Jak tylko wykonasz jedną operację read-modify-write, musisz przejść do operacji z blokadą lub użyć blokady.
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-06-22 06:39:39
EDIT: Jak wspomniano w komentarzach, w dzisiejszych czasach chętnie używam Interlocked
dla przypadków pojedynczej zmiennej Gdzie jest oczywiście w porządku. Kiedy będzie bardziej skomplikowane, wrócę do zamykania...
Użycie volatile
nie pomoże, gdy trzeba zwiększyć-ponieważ odczyt i zapis są oddzielnymi instrukcjami. Inny wątek może zmienić wartość po przeczytaniu, ale przed odpisaniem.
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
2014-03-20 07:38:27
"volatile
" nie zastępuje Interlocked.Increment
! Po prostu upewnia się, że zmienna nie jest buforowana, ale używana bezpośrednio.
Zwiększenie zmiennej wymaga w rzeczywistości trzech operacji:
- Czytaj
- increment
- napisz
Interlocked.Increment
wykonuje wszystkie trzy części jako jedną operację atomową.
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-09-07 16:11:03
Albo blokada lub blokada jest tym, czego szukasz.
Volatile zdecydowanie nie jest tym, czego szukasz - po prostu mówi kompilatorowi, aby traktował zmienną jako zawsze zmieniającą się, nawet jeśli bieżąca ścieżka kodu pozwala kompilatorowi zoptymalizować odczyt z pamięci w przeciwnym razie.
Np.
while (m_Var)
{ }
Jeśli m_Var jest ustawione na false w innym wątku, ale nie jest zadeklarowane jako volatile, kompilator może uczynić go nieskończoną pętlą (ale nie oznacza to, że zawsze będzie) poprzez sprawdza w rejestrze procesora (np. EAX, ponieważ do tego właśnie został pobrany m_Var od samego początku) zamiast wysyłać kolejny odczyt do lokalizacji pamięci m_Var (może to być buforowane - nie wiemy i nie obchodzi nas to i to jest punkt spójności bufora x86/x64). Wszystkie wcześniejsze posty innych osób, które wspominały o zmianie kolejności instrukcji, pokazują, że nie rozumieją architektur x86/x64. Volatile nie tworzy barier odczytu/zapisu, jak sugerują wcześniejsze posty mówiące "zapobiega ponownemu uporządkowaniu". W rzeczywistości, dzięki protokołowi MESI, mamy gwarancję, że wynik, który odczytujemy, jest zawsze taki sam na wszystkich procesorach, niezależnie od tego, czy rzeczywiste wyniki zostały wycofane do pamięci fizycznej, czy po prostu znajdują się w pamięci podręcznej lokalnego procesora. Nie będę się zbytnio zagłębiał w szczegóły tego, ale bądźcie pewni, że jeśli to pójdzie źle, Intel / AMD prawdopodobnie wyda procesor recall! Oznacza to również, że nie musimy przejmować się realizacją poza zleceniem itp. Wyniki są zawsze gwarantowane aby przejść na emeryturę w porządku-inaczej jesteśmy wypchani!
Z interlocked Increment, procesor musi wyjść, pobrać wartość z podanego adresu, następnie inkrementować i odpisać-wszystko to mając wyłączną własność całej linii pamięci podręcznej (lock xadd), aby upewnić się, że żadne inne procesory nie mogą zmodyfikować jej wartości.
Z volatile, nadal będziesz mieć tylko 1 Instrukcję (zakładając, że JIT jest wydajny tak jak powinien) - inc dword ptr [m_Var]. Jednak procesor (cpuA) nie prosi o wyłączną własność linii pamięci podręcznej podczas robienia wszystkiego z wersją z blokadą. Jak możesz sobie wyobrazić, oznacza to, że inne procesory mogą zapisać zaktualizowaną wartość z powrotem do m_Var po jej odczytaniu przez cpuA. Więc zamiast teraz zwiększyć wartość dwukrotnie, kończy się tylko raz.
Mam nadzieję, że to rozwiąże problem.Aby uzyskać więcej informacji, zobacz "zrozumieć wpływ technik Low-Lock w aplikacjach wielowątkowych" - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx
P. s. Co skłoniło do tej bardzo późnej odpowiedzi? Wszystkie odpowiedzi były tak rażąco niepoprawne (szczególnie ta oznaczona jako odpowiedź) w swoim wyjaśnieniu, że musiałem to wyjaśnić każdemu, kto to czyta. wzrusza ramionami
P. P. S. zakładam, że celem jest x86 / x64, a nie IA64 (ma inny model pamięci). Zauważ, że specyfikacja ECMA Microsoftu jest spieprzona, ponieważ określa najsłabszy model pamięci zamiast najsilniejszego (zawsze lepiej jest określić najmocniejszy model pamięci, aby był spójny na różnych platformach - w przeciwnym razie kod, który działałby 24-7 na x86/x64, może w ogóle nie działać na IA64, chociaż Intel zaimplementował podobnie silny model pamięci dla IA64) - Microsoft przyznał to sam - {26]} http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx .
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-07-07 11:50:40
Zablokowane funkcje nie blokują się. Są atomowe, co oznacza, że mogą zakończyć się bez możliwości przełączania kontekstu Podczas przyrostu. Więc nie ma szans na impas lub czekać.
Powiedziałbym, że zawsze powinieneś woleć to od blokady i inkrementacji.
Volatile jest przydatny, jeśli trzeba zapisać w jednym wątku, aby odczytać w innym, i jeśli chcesz, aby optymalizator nie zmieniał kolejności operacji na zmiennej (ponieważ rzeczy dzieją się w innym wątku, że optimizer o tym nie wie). To ortogonalny wybór, jak możesz zwiększyć.
Jest to naprawdę dobry artykuł, jeśli chcesz przeczytać więcej o kodzie bez blokady i właściwym sposobie podejścia do jego pisania
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-30 19:33:39
Lock(...) działa, ale może zablokować wątek i spowodować impas, jeśli inny kod używa tych samych blokad w niezgodny sposób.
* jest prawidłowy sposób, aby to zrobić ... znacznie mniej kosztów, jak nowoczesne procesory obsługują to jako prymitywne.
Lotna sama w sobie nie jest poprawna. Wątek próbujący odzyskać, a następnie odpisać zmodyfikowaną wartość może nadal być w konflikcie z innym wątkiem wykonującym to samo.
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-30 19:32:09
Popieram odpowiedź Jona Skeeta i chcę dodać następujące linki dla wszystkich, którzy chcą dowiedzieć się więcej o" lotnych " i Interlocked:
Atomiczność, zmienność i niezmienność są różne, część druga
Atomiczność, zmienność i niezmienność są różne, Część trzecia
Sayonara - (Wayback Maszynowa migawka Webloga Joe Duffy ' ego, jak pojawiła się w 2012)
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-27 17:54:45
Zrobiłem test, aby zobaczyć, jak teoria naprawdę działa: kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html . mój test był bardziej skoncentrowany na CompareExchnage, ale wynik przyrostu jest podobny. Blokowane nie jest konieczne szybsze w środowisku wielu procesorów. Oto wynik testu dla przyrostu na 2-letnim serwerze CPU 16. Należy pamiętać, że test obejmuje również bezpieczny odczyt po zwiększeniu, co jest typowe w świecie rzeczywistym.
D:\>InterlockVsMonitor.exe 16
Using 16 threads:
InterlockAtomic.RunIncrement (ns): 8355 Average, 8302 Minimal, 8409 Maxmial
MonitorVolatileAtomic.RunIncrement (ns): 7077 Average, 6843 Minimal, 7243 Maxmial
D:\>InterlockVsMonitor.exe 4
Using 4 threads:
InterlockAtomic.RunIncrement (ns): 4319 Average, 4319 Minimal, 4321 Maxmial
MonitorVolatileAtomic.RunIncrement (ns): 933 Average, 802 Minimal, 1018 Maxmial
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
2012-08-21 12:11:58
Przeczytaj Threading w C# referencji. Obejmuje tajniki twojego pytania. Każdy z trzech mają różne cele i skutki uboczne.
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-30 19:33:32