Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wielowątkowość wymaga starannego programowania. W przypadku większości zadań można zmniejszyć złożoność przez kolejkowanie żądań do wykonania przez wątki puli wątków. Ten temat dotyczy bardziej trudnych sytuacji, takich jak koordynowanie pracy wielu wątków lub obsługa wątków, które blokują.
Uwaga / Notatka
Począwszy od programu .NET Framework 4, biblioteka równoległa zadań i PLINQ udostępniają interfejsy API, które zmniejszają złożoność i ryzyko programowania wielowątkowego. Aby uzyskać więcej informacji, zobacz Parallel Programming in .NET (Programowanie równoległe na platformie .NET).
Zakleszczenia i warunki wyścigu
Wielowątkowość rozwiązuje problemy z przepustowością i czasem reakcji, ale w ten sposób wprowadza nowe problemy: zakleszczenia i sytuacje wyścigowe.
Zakleszczenia
Zakleszczenie występuje, gdy każdy z dwóch wątków próbuje zablokować zasób, który jest już zablokowany przez drugi wątek. Żaden z wątków nie może poczynić dalszych postępów.
Wiele metod zarządzanych klas wątków zapewnia limity czasu, aby ułatwić wykrywanie zakleszczeń. Na przykład poniższy kod próbuje uzyskać blokadę obiektu o nazwie lockObject. Jeśli blokada nie zostanie uzyskana w ciągu 300 milisekund, Monitor.TryEnter zwraca wartość false.
If Monitor.TryEnter(lockObject, 300) Then
Try
' Place code protected by the Monitor here.
Finally
Monitor.Exit(lockObject)
End Try
Else
' Code to execute if the attempt times out.
End If
if (Monitor.TryEnter(lockObject, 300)) {
try {
// Place code protected by the Monitor here.
}
finally {
Monitor.Exit(lockObject);
}
}
else {
// Code to execute if the attempt times out.
}
Warunki wyścigu
Sytuacja wyścigu to usterka, która występuje, gdy wynik programu zależy od tego, który z co najmniej dwóch wątków jako pierwszy dotrze do określonego bloku kodu. Uruchomienie programu wiele razy generuje różne wyniki, a wynik jakiegokolwiek przebiegu nie można przewidzieć.
Prostym przykładem stanu wyścigu jest zwiększanie pola. Załóżmy, że klasa ma prywatne pole statyczne (udostępnione w Visual Basic), które jest zwiększane za każdym razem, gdy wystąpienie klasy jest tworzone, przy użyciu kodu takiego jak objCt++; (C#) lub objCt += 1 (Visual Basic). Ta operacja wymaga załadowania wartości z objCt do rejestru, przyrostowania wartości i przechowywania jej w pliku objCt.
W aplikacji wielowątkowej wątek, który załadował i zwiększył wartość, może zostać zastąpiony przez inny wątek, który wykonuje wszystkie trzy kroki; gdy pierwszy wątek wznawia wykonywanie i przechowuje jego wartość, zastępuje objCt ją bez uwzględniania faktu, że wartość zmieniła się w międzyczasie.
Ten warunek wyścigu można łatwo uniknąć, korzystając z metod klasy Interlocked, takich jak Interlocked.Increment. Aby zapoznać się z innymi technikami synchronizowania danych między wieloma wątkami, zobacz Synchronizowanie danych wielowątkowych.
Sytuacje wyścigowe mogą również wystąpić, gdy synchronizujesz działania wielu wątków. Za każdym razem, gdy piszesz linię kodu, musisz rozważyć, co może się zdarzyć, jeśli wątek zostałby przerwany przed wykonaniem tej linii (lub przed jakąkolwiek maszynową instrukcją wchodzącą w skład tej linii), a inny wątek ją przejął.
Statyczne członki i konstruktory statyczne
Klasa nie jest inicjowana, dopóki konstruktor klasy (static konstruktor w języku C#, Shared Sub New w Visual Basic) nie zostanie uruchomiony. Aby zapobiec wykonywaniu kodu na typie, który nie jest zainicjowany, Common Language Runtime blokuje wszystkie wywołania z innych wątków do static składowych klasy (Shared składowych w Visual Basic), dopóki konstruktor klasy nie zakończy działania.
Jeśli na przykład konstruktor klasy uruchamia nowy wątek, a procedura wątku wywołuje składową static klasy, nowy wątek jest blokowany, dopóki konstruktor klasy się nie zakończy.
Dotyczy to dowolnego typu, który może mieć konstruktor static.
Liczba procesorów
Niezależnie od tego, czy istnieje wiele procesorów, czy tylko jeden procesor dostępny w systemie, może mieć wpływ na architekturę wielowątkową. Aby uzyskać więcej informacji, zobacz Liczba procesorów.
Environment.ProcessorCount Użyj właściwości , aby określić liczbę procesorów dostępnych w czasie wykonywania.
Zalecenia ogólne
Podczas korzystania z wielu wątków należy wziąć pod uwagę następujące wskazówki:
Nie należy używać Thread.Abort do przerywania innych wątków. Wywoływanie
Abortw innym wątku jest podobne do rzucania wyjątku w tym wątku, nie wiedząc, jaki punkt ten wątek osiągnął w trakcie jego przetwarzania.Nie używaj funkcji Thread.Suspend i Thread.Resume do synchronizowania działań wielu wątków. Używaj Mutex, ManualResetEvent, AutoResetEvent i Monitor.
Nie steruj wykonywaniem wątków roboczych z głównego programu (na przykład przy użyciu zdarzeń). Zamiast tego zaprojektuj program tak, aby wątki robocze odpowiadały za oczekiwanie na dostępność pracy, wykonanie jej i powiadamianie innych części programu po zakończeniu. Jeśli wątki robocze nie blokują, rozważ użycie puli wątków. Monitor.PulseAll jest przydatna w sytuacjach, gdy wątki robocze są blokowane.
Nie używaj typów jako obiektów blokady. Oznacza to, że należy unikać kodu, takiego jak
lock(typeof(X))w języku C# lubSyncLock(GetType(X))w Visual Basic, oraz unikać używania Monitor.Enter z obiektami Type. W przypadku danego typu istnieje tylko jedno wystąpienie System.Type dla domeny aplikacji. Jeśli typ blokady jest publiczny, kod inny niż własny może przyjmować blokady, co prowadzi do zakleszczenia. Aby uzyskać więcej informacji o problemach, zobacz Najlepsze praktyki dotyczące niezawodności.Zachowaj ostrożność podczas blokowania wystąpień, na przykład
lock(this)w języku C# lubSyncLock(Me)Visual Basic. Jeśli inny kod w aplikacji, poza typem, przyjmuje blokadę obiektu, mogą wystąpić zakleszczenia.Upewnij się, że wątek, który wszedł do monitora, zawsze opuszcza ten monitor, nawet jeśli wystąpi wyjątek, gdy wątek jest w monitorze. Instrukcja lock języka C# i instrukcja SyncLock języka Visual Basic zapewniają to zachowanie automatycznie, stosując blok finally , aby upewnić się, że Monitor.Exit jest wywołane. Jeśli nie możesz upewnić się, że zostanie wywołana funkcja Exit , rozważ zmianę projektu tak, aby korzystała z usługi Mutex. Mutex jest automatycznie zwalniany, gdy wątek, który go posiada, zakończy działanie.
Należy używać wielu wątków do zadań wymagających różnych zasobów i unikać przypisywania wielu wątków do jednego zasobu. Na przykład każde zadanie związane z operacjami we/wy korzysta z posiadania własnego wątku, ponieważ ten wątek blokuje się podczas operacji we/wy, co pozwala na wykonywanie innych wątków. Wprowadzenie danych przez użytkownika to kolejny zasób, który korzysta z dedykowanego wątku. Na komputerze z jednym procesorem zadanie obejmujące intensywne obliczenia współistnieją z danymi wejściowymi użytkownika i zadaniami obejmującymi operacje we/wy, ale wiele zadań intensywnie korzystających z obliczeń zmaga się ze sobą.
Rozważ użycie metod klasy Interlocked do prostych zmian stanu, zamiast używać instrukcji
lock(SyncLockw Visual Basic). Instrukcjalockjest dobrym narzędziem ogólnego przeznaczenia, ale klasa Interlocked oferuje lepszą wydajność dla aktualizacji, które muszą być atomowe. Wewnętrznie wykonuje pojedynczy prefiks lock, jeśli nie ma konfliktu. W przeglądach kodu zwracaj uwagę na kod, jak pokazano w poniższych przykładach. W pierwszym przykładzie zmienna stanu jest zwiększana:SyncLock lockObject myField += 1 End SyncLocklock(lockObject) { myField++; }Wydajność można poprawić przy użyciu Increment metody zamiast instrukcji
lockw następujący sposób:System.Threading.Interlocked.Increment(myField)System.Threading.Interlocked.Increment(myField);Uwaga / Notatka
Użyj metody Add dla atomowych przyrostów większych niż 1.
W drugim przykładzie zmienna typu odwołania jest aktualizowana tylko wtedy, gdy jest to odwołanie o wartości null (
Nothingw Visual Basic).If x Is Nothing Then SyncLock lockObject If x Is Nothing Then x = y End If End SyncLock End Ifif (x == null) { lock (lockObject) { x ??= y; } }Wydajność można poprawić przy użyciu metody CompareExchange w następujący sposób:
System.Threading.Interlocked.CompareExchange(x, y, Nothing)System.Threading.Interlocked.CompareExchange(ref x, y, null);Uwaga / Notatka
Przeciążenie metody CompareExchange<T>(T, T, T) zapewnia typowo bezpieczną alternatywę dla typów referencyjnych.
Zalecenia dotyczące bibliotek klas
Przy projektowaniu bibliotek klas na potrzeby wielowątkowości, należy wziąć pod uwagę następujące wskazówki:
Unikaj konieczności synchronizacji, jeśli to możliwe. Dotyczy to szczególnie intensywnie używanego kodu. Na przykład algorytm można dostosować tak, aby tolerował sytuację wyścigu, zamiast ją eliminować. Niepotrzebna synchronizacja zmniejsza wydajność i tworzy możliwość zakleszczenia i warunków wyścigu.
Domyślne zabezpieczanie wątków statycznych (
Sharedw języku Visual Basic).Nie należy domyślnie zabezpieczać wątku danych wystąpienia. Dodanie blokad w celu utworzenia kodu bezpiecznego wątkowo zmniejsza wydajność, zwiększa rywalizację o blokadę i tworzy możliwość zakleszczenia. W typowych modelach aplikacji tylko jeden wątek jednocześnie wykonuje kod użytkownika, co minimalizuje potrzebę bezpieczeństwa wątków. Z tego powodu biblioteki klas platformy .NET nie są domyślnie bezpieczne dla wątków.
Unikaj udostępniania metod statycznych, które zmieniają stan statyczny. W typowych scenariuszach serwera stan statyczny jest współużytkowany między żądaniami, co oznacza, że różne wątki mogą wykonywać ten sam kod w tym samym czasie. To otwiera możliwość wystąpienia błędów wątkowości. Rozważ użycie wzorca projektowego, który enkapsuluje dane w instancjach, które nie są udostępniane między żądaniami. Ponadto, jeśli dane statyczne są synchronizowane, wywołania między metodami statycznymi, które zmieniają stan, mogą spowodować zakleszczenia lub nadmiarową synchronizację, co negatywnie wpływa na wydajność.