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 na public 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ć?

Author: Sam, 2008-09-30

9 answers

Worst (won ' t actually work)

Zmień modyfikator dostępu z counter na public 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:

  1. metody blokowane są jednocześnie bezpieczne dla dowolnej liczby rdzeni lub procesorów.
  2. metody blokowane stosują pełne ogrodzenie wokół instrukcje wykonują, więc zmiana kolejności nie ma miejsca.
  3. 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.

 765
Author: Orion Edwards,
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.

Osobiście prawie zawsze zamykam-łatwiej jest dostać prawo w sposób, który jest oczywiście prawo niż zmienność lub przeplot.Przyrost. Jeśli O mnie chodzi, multi-threading bez blokady jest dla prawdziwych ekspertów od gwintowania, z których nie jestem jednym. Jeśli Joe Duffy i jego zespół zbudują ładne biblioteki, które będą paralelizować rzeczy bez tak dużego blokowania, jak coś bym zbudował, to jest fantastyczne, i będę go używać w mgnieniu oka - ale kiedy robię threading siebie, staram się utrzymać to proste.
 127
Author: Jon Skeet,
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:

  1. Czytaj
  2. increment
  3. napisz

Interlocked.Increment wykonuje wszystkie trzy części jako jedną operację atomową.

 41
Author: Michael Damatov,
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 .

 41
Author: Zach Saw,
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

Http://www.ddj.com/hpc-high-performance-computing/210604448

 15
Author: Lou Franco,
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.

 11
Author: Rob Walker,
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
 8
Author: zihotki,
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
 7
Author: Kenneth Xu,
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.

 4
Author: spoulson,
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