Podczas pisania aplikacji wielowątkowych jednym z najczęstszych problemów są warunki wyścigowe.
Moje pytania do społeczności to:
Jaki jest stan wyścigu?
Jak je wykrywasz?
Jak sobie z nimi radzisz?
Wreszcie, w jaki sposób zapobiegasz ich występowaniu?
multithreading
concurrency
terminology
race-condition
bmurphy1976
źródło
źródło
Odpowiedzi:
Wyścig występuje, gdy dwa lub więcej wątków może uzyskać dostęp do współużytkowanych danych i próbują je zmienić w tym samym czasie. Ponieważ algorytm planowania wątków może w dowolnym momencie przełączać się między wątkami, nie znasz kolejności, w której wątki będą próbowały uzyskać dostęp do udostępnionych danych. Dlatego wynik zmiany danych zależy od algorytmu szeregowania wątków, tzn. Oba wątki „ścigają się” w celu uzyskania dostępu / zmiany danych.
Często występują problemy, gdy jeden wątek wykonuje polecenie „sprawdź, a następnie działaj” (np. „Sprawdź”, jeśli wartość wynosi X, a następnie „działaj”, aby zrobić coś, co zależy od wartości będącej X), a inny wątek robi coś z wartością w między „czekiem” a „aktem”. Na przykład:
Chodzi o to, że y może wynosić 10, lub może być czymkolwiek, w zależności od tego, czy inny wątek zmienił x pomiędzy sprawdzeniem a działaniem. Nie masz prawdziwej wiedzy.
Aby zapobiec występowaniu warunków wyścigu, zwykle blokujesz udostępnione dane, aby zapewnić, że tylko jeden wątek może uzyskać dostęp do danych na raz. Oznaczałoby to coś takiego:
źródło
„Warunek wyścigu” istnieje, gdy wielowątkowy (lub w inny sposób równoległy) kod, który miałby dostęp do zasobu współdzielonego, mógłby to zrobić w taki sposób, aby spowodować nieoczekiwane wyniki.
Weź ten przykład:
Jeśli miałeś 5 wątków wykonujących ten kod jednocześnie, wartość x NIE NALEŻY skończyć na 50 000 000. W rzeczywistości zmieniałby się przy każdym uruchomieniu.
Wynika to z tego, że aby każdy wątek mógł zwiększyć wartość x, muszą wykonać następujące czynności: (oczywiście uproszczone)
Dowolny wątek może być na dowolnym etapie tego procesu w dowolnym momencie i może nadepnąć na siebie, gdy zaangażowany jest zasób współdzielony. Stan x może być zmieniony przez inny wątek podczas odczytywania x i gdy jest on ponownie zapisywany.
Powiedzmy, że wątek pobiera wartość x, ale jeszcze jej nie zapisał. Inny wątek może również pobrać tę samą wartość x (ponieważ żaden wątek go jeszcze nie zmienił), a następnie oba zapisywałyby tę samą wartość (x + 1) z powrotem w x!
Przykład:
Można uniknąć warunków wyścigu, stosując jakiś mechanizm blokujący przed kodem, który uzyskuje dostęp do udostępnionego zasobu:
Tutaj odpowiedź pojawia się za każdym razem w wysokości 50 000 000.
Aby uzyskać więcej informacji na temat blokowania, wyszukaj: mutex, semafor, sekcja krytyczna, zasób udostępniony.
źródło
Planujesz pójść do kina o 17:00. Zapytaj o dostępność biletów o 16.00. Przedstawiciel mówi, że są dostępne. Odprężasz się i docierasz do okna biletu na 5 minut przed koncertem. Jestem pewien, że możesz zgadnąć, co się stanie: to jest dom pełen. Problem polegał na czasie między sprawdzeniem a działaniem. Zapytałeś o 4 i działałeś o 5. W międzyczasie ktoś inny wziął bilety. To warunek wyścigu - w szczególności scenariusz warunków wyścigu „sprawdź, a następnie działaj”.
Przegląd kodu religijnego, wielowątkowe testy jednostkowe. Nie ma skrótu. Pojawia się niewiele wtyczek Eclipse, ale nic jeszcze nie jest stabilne.
Najlepiej byłoby stworzyć funkcje wolne od skutków ubocznych i bezpaństwowe, w miarę możliwości używać niezmiennych. Ale nie zawsze jest to możliwe. Pomoże to w użyciu java.util.concurrent.atomic, współbieżnych struktur danych, właściwej synchronizacji i współbieżności opartej na aktorach.
Najlepszym źródłem współbieżności jest JCIP. Możesz również uzyskać więcej szczegółowych informacji na temat powyższego wyjaśnienia tutaj .
źródło
Istnieje istotna różnica techniczna między warunkami wyścigu a wyścigami danych. Większość odpowiedzi wydaje się przyjmować założenie, że te terminy są równoważne, ale nie są.
Wyścig danych ma miejsce, gdy 2 instrukcje uzyskują dostęp do tego samego miejsca w pamięci, co najmniej jeden z tych dostępów to zapis i przed złożeniem zamówienia między tymi dostępami nie ma miejsca. Teraz to, co dzieje się przed złożeniem zamówienia, jest przedmiotem wielu dyskusji, ale generalnie pary ulock-lock na tej samej zmiennej blokującej i pary sygnał oczekiwania na tej samej zmiennej warunkowej wywołują zdarzenie poprzedzające zamówienie.
Warunek wyścigu jest błędem semantycznym. Jest to usterka występująca w czasie lub porządku zdarzeń, która prowadzi do błędnego zachowania programu .
Wiele warunków wyścigu może być (i faktycznie jest) spowodowanych wyścigami danych, ale nie jest to konieczne. W rzeczywistości wyścigi danych i warunki wyścigu nie są ani koniecznym, ani wystarczającym warunkiem dla siebie nawzajem. Ten post na blogu również bardzo dobrze wyjaśnia różnicę na prostym przykładzie transakcji bankowej. Oto kolejny prosty przykład, który wyjaśnia różnicę.
Teraz, kiedy dopracowaliśmy terminologię, spróbujmy odpowiedzieć na pierwotne pytanie.
Biorąc pod uwagę, że warunki rasowe to błędy semantyczne, nie ma ogólnego sposobu ich wykrywania. Wynika to z faktu, że nie ma możliwości posiadania zautomatyzowanej wyroczni, która w ogólnym przypadku byłaby w stanie odróżnić prawidłowe i niepoprawne zachowanie programu. Wykrywanie rasy jest nierozstrzygalnym problemem.
Z drugiej strony rasy danych mają precyzyjną definicję, która niekoniecznie odnosi się do poprawności, a zatem można je wykryć. Istnieje wiele odmian detektorów wyścigów danych (wykrywanie wyścigów danych statycznych / dynamicznych, wykrywanie wyścigów danych na podstawie lockset, wykrywanie wyścigów na podstawie zdarzeń sprzed zdarzeń, wykrywanie wyścigów hybrydowych). Najnowocześniejszym detektorem dynamicznych wyścigów danych jest ThreadSanitizer, który działa bardzo dobrze w praktyce.
Ogólnie rzecz biorąc, obsługa wyścigów danych wymaga pewnej dyscypliny programistycznej, zanim dojdzie do krawędzi między dostępem do współdzielonych danych (podczas opracowywania lub po ich wykryciu za pomocą wyżej wymienionych narzędzi). można tego dokonać za pomocą blokad, zmiennych warunkowych, semaforów itp. Można jednak zastosować różne paradygmaty programowania, takie jak przekazywanie komunikatów (zamiast pamięci współużytkowanej), które pozwalają uniknąć wyścigów danych przez konstrukcję.
źródło
W pewnym sensie kanoniczną definicją jest „ gdy dwa wątki uzyskują dostęp do tego samego miejsca w pamięci w tym samym czasie, a przynajmniej jeden z dostępów to zapis ”. W tej sytuacji wątek „czytnik” może otrzymać starą lub nową wartość, w zależności od tego, który wątek „wygrywa wyścig”. Nie zawsze jest to błąd - w rzeczywistości niektóre naprawdę owłosione algorytmy niskiego poziomu robią to celowo - ale ogólnie należy tego unikać. @ Steve Gury daje dobry przykład, kiedy może to stanowić problem.
źródło
Wyścig jest rodzajem błędu, który zdarza się tylko w pewnych warunkach czasowych.
Przykład: wyobraź sobie, że masz dwa wątki, A i B.
W wątku A:
W wątku B:
Jeśli wątek A zostanie zatrzymany tuż po sprawdzeniu tego obiektu. A nie jest zerowy, B zrobi to
a = 0
, a gdy wątek A zyska procesor, wykona „dzielenie przez zero”.Ten błąd występuje tylko wtedy, gdy wątek A jest blokowany tuż po instrukcji if, jest to bardzo rzadkie, ale może się zdarzyć.
źródło
Wyścig jest związany nie tylko z oprogramowaniem, ale także ze sprzętem. Właściwie termin ten został ukuty przez przemysł sprzętowy.
Według wikipedii :
Branża oprogramowania przyjęła ten termin bez modyfikacji, co utrudnia jego zrozumienie.
Musisz dokonać wymiany, aby zmapować go do świata oprogramowania:
Tak więc stan wyścigu w branży oprogramowania oznacza „dwa wątki” / „dwa procesy” ścigające się wzajemnie, aby „wpłynąć na jakiś wspólny stan”, a ostateczny wynik tego wspólnego stanu będzie zależał od pewnych subtelnych różnic czasowych, które mogą być spowodowane przez pewne określone kolejność uruchamiania wątków / procesów, planowanie wątków / procesów itp.
źródło
Warunkiem wyścigu jest sytuacja w programowaniu współbieżnym, w której dwa współbieżne wątki lub procesy konkurują o zasób, a wynikowy stan końcowy zależy od tego, kto pobierze zasób jako pierwszy.
źródło
Warunki wyścigu występują w aplikacjach wielowątkowych lub systemach wieloprocesowych. Warunek wyścigu, w swojej najbardziej podstawowej formie, jest wszystkim, co sprawia, że zakłada się, że dwie rzeczy nie w tym samym wątku lub procesie wystąpią w określonej kolejności, bez podjęcia kroków w celu zapewnienia, że tak się stanie. Dzieje się tak często, gdy dwa wątki przekazują wiadomości, ustawiając i sprawdzając zmienne składowe klasy, do której oba mają dostęp. Prawie zawsze występuje wyścig, gdy jeden wątek wywołuje tryb uśpienia, aby dać drugiemu czas na zakończenie zadania (chyba że sen jest w pętli z pewnym mechanizmem sprawdzającym).
Narzędzia do zapobiegania warunkom wyścigowym zależą od języka i systemu operacyjnego, ale niektóre z nich to muteksy, sekcje krytyczne i sygnały. Muteksy są dobre, gdy chcesz mieć pewność, że tylko Ty coś robisz. Sygnały są dobre, gdy chcesz mieć pewność, że ktoś już coś zrobił. Minimalizowanie udostępnionych zasobów może również pomóc w zapobieganiu nieoczekiwanym zachowaniom
Wykrywanie warunków wyścigu może być trudne, ale jest kilka znaków. Kod, który w dużej mierze opiera się na snach, jest podatny na warunki wyścigowe, więc najpierw sprawdź wezwania do spania w odpowiednim kodzie. Dodanie szczególnie długich snów może być również wykorzystane do debugowania w celu wymuszenia określonej kolejności zdarzeń. Może to być przydatne do odtworzenia zachowania, sprawdzenia, czy można go usunąć poprzez zmianę czasu rzeczy oraz do testowania wdrożonych rozwiązań. Uśpienia powinny zostać usunięte po debugowaniu.
Znakiem rozpoznawczym, że jeden ma warunek wyścigu, jest problem występujący sporadycznie tylko na niektórych maszynach. Typowymi błędami byłyby awarie i impasy. Po zalogowaniu powinieneś być w stanie znaleźć dotknięty obszar i wrócić do niego.
źródło
Microsoft opublikował naprawdę szczegółowy artykuł na temat warunków wyścigu i impasu. Najbardziej streszczonym streszczeniem byłby tytułowy akapit:
źródło
Sytuacja, w której proces jest krytycznie zależny od sekwencji lub czasu innych zdarzeń.
Na przykład procesor A i procesor B potrzebują identycznych zasobów do ich wykonania.
Istnieją narzędzia do automatycznego wykrywania warunków wyścigu:
Warunkiem wyścigu może być Mutex lub Semaphores . Działają jak blokada, umożliwiając procesowi pozyskiwanie zasobów w oparciu o określone wymagania, aby zapobiec wyścigowi.
Istnieją różne sposoby zapobiegania stanom wyścigowym, takie jak unikanie sekcji krytycznych .
źródło
Wyścig to niepożądana sytuacja, która występuje, gdy urządzenie lub system próbuje wykonać dwie lub więcej operacji jednocześnie, ale ze względu na naturę urządzenia lub systemu, operacje muszą być wykonane we właściwej kolejności, aby były wykonane poprawnie.
W pamięci komputera lub w pamięci może wystąpić wyścig, jeśli polecenia do odczytu i zapisu dużej ilości danych zostaną odebrane prawie w tym samym momencie, a maszyna podejmie próbę zastąpienia niektórych lub wszystkich starych danych, gdy te stare dane są nadal przetwarzane czytać. Rezultatem może być jedna lub więcej z następujących przyczyn: awaria komputera, „nielegalna operacja”, powiadomienie i zamknięcie programu, błędy odczytu starych danych lub błędy zapisu nowych danych.
źródło
Oto klasyczny przykład salda konta bankowego, który pomoże początkującym zrozumieć wątki w Javie w prosty sposób na warunki wyścigu:
źródło
Możesz zapobiec warunkom wyścigowym , jeśli użyjesz klas „Atomowych”. Powodem jest to, że wątek nie rozdziela operacji pobierania i ustawiania, przykład poniżej:
W rezultacie będziesz mieć 7 w linku „ai”. Chociaż wykonałeś dwie akcje, ale obie operacje potwierdzają ten sam wątek i żaden inny wątek nie będzie w to przeszkadzał, co oznacza brak warunków wyścigu!
źródło
Wypróbuj ten podstawowy przykład, aby lepiej zrozumieć warunki wyścigu:
źródło
Nie zawsze chcesz odrzucić warunek wyścigu. Jeśli masz flagę, która może być odczytywana i zapisywana przez wiele wątków, a ta flaga jest ustawiona na „zrobione” przez jeden wątek, tak aby inny wątek zatrzymał przetwarzanie, gdy flaga jest ustawiona na „zrobione”, nie chcesz, aby ten wyścig warunek ”do wyeliminowania. W rzeczywistości ten można nazwać łagodnym stanem rasy.
Jednak za pomocą narzędzia do wykrywania stanu wyścigu zostanie wykryte jako szkodliwy stan wyścigu.
Więcej informacji na temat warunków wyścigu tutaj, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx .
źródło
Rozważ operację, która musi wyświetlać liczbę, gdy tylko liczba zostanie zwiększona. tzn. jak tylko CounterThread zwiększy wartość DisplayThread musi wyświetlić ostatnio zaktualizowaną wartość.
Wynik
Tutaj CounterThread często otrzymuje blokadę i aktualizuje wartość, zanim DisplayThread ją wyświetli. Tutaj istnieje warunek wyścigu. Stan wyścigu można rozwiązać za pomocą synchronizacji
źródło
Warunek wyścigu jest niepożądaną sytuacją, która występuje, gdy dwa lub więcej procesów może uzyskać dostęp do wspólnych danych i zmienić je jednocześnie. Krytyczny problem z sekcją może powodować wyścig. Aby rozwiązać krytyczny warunek procesu, wyjęliśmy tylko jeden proces naraz, który wykonuje sekcję krytyczną.
źródło