Warunki wyścigu i zakleszczenia

Program Visual Basic .NET lub Visual Basic oferuje możliwość korzystania z wątków w aplikacjach Visual Basic po raz pierwszy. Wątki wprowadzają problemy z debugowaniem, takie jak warunki wyścigu i zakleszczenia. W tym artykule omówiono te dwa problemy.

Oryginalna wersja produktu: Visual Basic, Visual Basic .NET
Oryginalny numer KB: 317723

Kiedy występują warunki wyścigu

Stan wyścigu występuje, gdy dwa wątki uzyskują dostęp do współużytkowanej zmiennej w tym samym czasie. Pierwszy wątek odczytuje zmienną, a drugi wątek odczytuje tę samą wartość ze zmiennej. Następnie pierwszy wątek i drugi wątek wykonują operacje na wartości, a następnie ścigają się, aby zobaczyć, który wątek może zapisać wartość ostatnią do zmiennej udostępnionej. Wartość wątku, który zapisuje jego wartość ostatnio, jest zachowywana, ponieważ wątek zapisuje wartość, którą napisał poprzedni wątek.

Szczegóły i przykłady dotyczące warunku wyścigu

Każdy wątek jest przydzielany wstępnie zdefiniowany okres czasu do wykonania na procesorze. Po wygaśnięciu czasu przydzielonego dla wątku kontekst wątku jest zapisywany do następnego włączenia procesora, a procesor rozpoczyna wykonywanie następnego wątku.

Jak jednowierszowe polecenie może spowodować stan wyścigu

Zapoznaj się z poniższym przykładem, aby zobaczyć, jak występuje warunek wyścigu. Istnieją dwa wątki, a obie aktualizują współdzieloną zmienną o nazwie total (która jest reprezentowana w dword ptr ds:[031B49DCh] kodzie zestawu).

  • Wątek 1

    Total = Total + val1
    
  • Wątek 2

    Total = Total - val2
    

Kod zestawu (z numerami wierszy) z kompilacji poprzedniego kodu języka Visual Basic:

  • Wątek 1

    1. mov eax,dword ptr ds:[031B49DCh]
    2. add eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 7611097F
    6. mov dword ptr ds:[031B49DCh],eax
    
  • Wątek 2

    1. mov eax,dword ptr ds:[031B49DCh]
    2. sub eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 76110BE7
    6. mov dword ptr ds:[031B49DCh],eax
    

Patrząc na kod zestawu, można zobaczyć, ile operacji wykonuje procesor na niższym poziomie w celu wykonania prostego obliczenia dodawania. Wątek może być w stanie wykonać cały lub część kodu zestawu w czasie procesora. Teraz przyjrzyj się, jak występuje warunek wyścigu z tego kodu.

Total ma wartość 100, val1 ma wartość 50, a val2 wartość 15. Wątek 1 daje możliwość wykonania, ale wykonuje tylko kroki od 1 do 3. Oznacza to, że wątek 1 odczytuje zmienną i zakończył dodawanie. Wątek 1 czeka teraz na zapisanie nowej wartości 150. Po zatrzymaniu wątku 1 wątek 2 zostanie wykonany całkowicie. Oznacza to, że zapisał wartość obliczoną (85) na zmienną Total. Na koniec wątek 1 odzyskuje kontrolę i kończy wykonywanie. Zapisuje wartość (150). W związku z tym po zakończeniu wątku 1 wartość wynosi Total teraz 150 zamiast 85.

Możesz zobaczyć, jak może to być poważnym problemem. Jeśli jest to program bankowy, klient będzie miał pieniądze na swoim koncie, które nie powinny być obecne.

Ten błąd jest losowy, ponieważ istnieje możliwość ukończenia wykonywania wątku 1 przed upływem czasu procesora, a następnie wątku 2 może rozpocząć wykonywanie. Jeśli wystąpią te zdarzenia, problem nie występuje. Wykonywanie wątku jest nieokreślone, dlatego nie można kontrolować czasu ani kolejności wykonywania. Należy również pamiętać, że wątki mogą być wykonywane inaczej w środowisku uruchomieniowym i w trybie debugowania. Ponadto można zobaczyć, że jeśli wykonasz każdy wątek w serii, błąd nie wystąpi. Ta losowość sprawia, że te błędy są znacznie trudniejsze do śledzenia i debugowania.

Aby zapobiec wystąpieniu warunków wyścigu, można zablokować współużytkowane zmienne, aby tylko jeden wątek w danym momencie miał dostęp do udostępnionej zmiennej. Zrób to oszczędnie, ponieważ jeśli zmienna jest zablokowana w wątku 1 i Thread 2 również wymaga zmiennej, wykonanie wątku 2 zostanie zatrzymane, podczas gdy wątek 2 czeka na wątek 1, aby zwolnić zmienną. (Aby uzyskać więcej informacji, zobacz SyncLock sekcję Odwołania w tym artykule).

Objawy stanu wyścigu

Najczęstszym objawem warunku wyścigu są nieprzewidywalne wartości zmiennych, które są współdzielone między wieloma wątkami. Wynika to z nieprzewidywalności kolejności wykonywania wątków. Czasami jeden wątek wygrywa, a czasami drugi wątek wygrywa. W innych przypadkach wykonywanie działa poprawnie. Ponadto, jeśli każdy wątek jest wykonywany oddzielnie, wartość zmiennej działa poprawnie.

Gdy występują zakleszczenia

Zakleszczenie występuje, gdy dwa wątki blokują w tym samym czasie inną zmienną, a następnie próbują zablokować zmienną, która jest już zablokowana przez drugi wątek. W związku z tym każdy wątek przestaje wykonywać i czeka na zwolnienie zmiennej przez drugi wątek. Ponieważ każdy wątek przechowuje zmienną, której chce drugi wątek, nic się nie dzieje, a wątki pozostają zakleszczone.

Szczegóły i przykłady zakleszczenia

Poniższy kod zawiera dwa obiekty LeftVal i RightVal:

  • Wątek 1

    SyncLock LeftVal
        SyncLock RightVal
            'Perform operations on LeftVal and RightVal that require read and write.
        End SyncLock
    End SyncLock
    
  • Wątek 2

    SyncLock RightVal
        SyncLock LeftVal
            'Perform operations on RightVal and LeftVal that require read and write.
        End SyncLock
    End SyncLock
    

Zakleszczenie występuje, gdy wątek 1 może zablokować LeftValwartość . Procesor zatrzymuje wykonywanie wątku 1 i rozpoczyna wykonywanie wątku 2. Wątek 2 blokuje RightVal , a następnie próbuje zablokować LeftVal. Ponieważ LeftVal jest zablokowany, thread 2 zatrzymuje się i czeka na LeftVal wydanie. Ponieważ wątek 2 jest zatrzymany, wątek 1 może kontynuować wykonywanie. Wątek 1 próbuje zablokować RightVal , ale nie, ponieważ thread 2 zablokował go. W związku z tym wątek 1 zaczyna czekać, aż funkcja RightVal stanie się dostępna. Każdy wątek czeka na drugi wątek, ponieważ każdy wątek zablokował zmienną, na którą czeka drugi wątek, a żaden z wątków nie odblokuje zmiennej, która jest przechowywana.

Zakleszczenie nie zawsze występuje. Jeśli wątek 1 wykonuje oba blokady przed zatrzymaniem procesora, Thread 1 może wykonać jego operacje, a następnie odblokować udostępnioną zmienną. Po odblokowaniu zmiennej Thread 1 wątek 2 może kontynuować wykonywanie zgodnie z oczekiwaniami.

Ten błąd wydaje się oczywisty, gdy te fragmenty kodu są umieszczane obok siebie, ale w praktyce kod może występować w osobnych modułach lub obszarach kodu. Jest to trudny błąd do śledzenia, ponieważ z tego samego kodu może wystąpić zarówno poprawne wykonanie, jak i nieprawidłowe wykonanie.

Objawy zakleszczenia

Typowym objawem zakleszczenia jest to, że program lub grupa wątków przestaje odpowiadać. Jest to również nazywane zawieszeniem. Co najmniej dwa wątki czekają na zmienną zablokowaną przez drugi wątek. Wątki nie są kontynuowane, ponieważ żaden wątek nie zwolni zmiennej, dopóki nie uzyska inną zmienną. Cały program może się zawiesić, jeśli program czeka na jeden lub oba te wątki do ukończenia wykonywania.

Co to jest wątek

Procesy służą do oddzielania różnych aplikacji wykonywanych w określonym czasie na jednym komputerze. System operacyjny nie wykonuje procesów, ale wątki. Wątek jest jednostką wykonywania. System operacyjny przydziela czas procesora wątkowi na potrzeby wykonywania zadań wątku. Pojedynczy proces może zawierać wiele wątków wykonywania. Każdy wątek zachowuje własne procedury obsługi wyjątków, planowanie priorytetów i zestaw struktur używanych przez system operacyjny do zapisywania kontekstu wątku, jeśli wątek nie może ukończyć wykonywania w czasie, gdy został przypisany do procesora. Kontekst jest przechowywany do następnego momentu odebrania czasu procesora przez wątek. Kontekst zawiera wszystkie informacje wymagane przez wątek, aby bezproblemowo kontynuować jego wykonywanie. Te informacje obejmują zestaw rejestrów procesora wątku oraz stos wywołań wewnątrz przestrzeni adresowej procesu hosta.

Informacje

Aby uzyskać więcej informacji, wyszukaj w pomocy programu Visual Studio następujące słowa kluczowe:

  • SyncLock. Umożliwia zablokowanie obiektu. Jeśli inny wątek próbuje zablokować ten sam obiekt, zostanie zablokowany do momentu wydania pierwszego wątku. Należy używać SyncLock ostrożnie, ponieważ problemy mogą wynikać z nieprawidłowego użycia funkcji SyncLock. Na przykład to polecenie może zapobiec warunkom wyścigu, ale powodować zakleszczenia.

  • InterLocked. Umożliwia wybór zestawu operacji bezpiecznych wątkowo dla podstawowych zmiennych liczbowych.

Aby uzyskać więcej informacji, zobacz Wątki i Wątki.