Aby uzyskać najwyższą wydajność z współbieżnie wykonywanymi jednostkami, napisz własną pulę wątków, w której pula obiektów Thread jest tworzona podczas uruchamiania i przejdź do blokowania (wcześniej zawieszonego), czekając na kontekst do uruchomienia (obiekt ze standardowym interfejsem zaimplementowanym przez Twój kod).
Tak wiele artykułów na temat zadań, wątków i puli wątków platformy .NET nie zawiera informacji niezbędnych do podjęcia decyzji o wydajności. Ale kiedy je porównasz, wygrywają wątki, a zwłaszcza pula wątków. Są najlepiej dystrybuowane między procesorami i uruchamiają się szybciej.
Należy omówić fakt, że główna jednostka wykonawcza systemu Windows (w tym Windows 10) jest wątkiem, a narzut przełączania kontekstu systemu operacyjnego jest zwykle pomijalny. Mówiąc najprościej, nie byłem w stanie znaleźć przekonujących dowodów na wiele z tych artykułów, niezależnie od tego, czy w artykule podano wyższą wydajność poprzez oszczędność przełączania kontekstu, czy lepsze wykorzystanie procesora.
Teraz trochę realizmu:
Większość z nas nie potrzebuje, aby nasza aplikacja była deterministyczna, a większość z nas nie ma tła z wątkami, co często wiąże się na przykład z tworzeniem systemu operacyjnego. To, co napisałem powyżej, nie jest dla początkującego.
Dlatego najważniejsze może być omówienie tego, co jest łatwe do zaprogramowania.
Jeśli utworzysz własną pulę wątków, będziesz mieć trochę do zrobienia, ponieważ będziesz musiał zająć się śledzeniem stanu wykonania, jak symulować zawieszenie i wznowienie oraz jak anulować wykonanie - w tym w całej aplikacji zamknąć. Być może będziesz musiał się również zastanowić, czy chcesz dynamicznie rozwijać swoją pulę, a także jakie ograniczenia pojemności będą miały Twoja pula. Mogę napisać taki framework w godzinę, ale to dlatego, że robiłem to wiele razy.
Być może najłatwiejszym sposobem napisania jednostki wykonawczej jest użycie zadania. Piękno zadania polega na tym, że możesz go utworzyć i uruchomić w kodzie (chociaż może być wymagana ostrożność). Możesz przekazać token anulowania do obsługi, gdy chcesz anulować zadanie. Ponadto stosuje podejście obiecujące do łączenia zdarzeń w łańcuchy i może zwrócić określony typ wartości. Co więcej, dzięki async i await istnieje więcej opcji, a Twój kod będzie bardziej przenośny.
W istocie ważne jest, aby zrozumieć zalety i wady funkcji Tasks, Threads i .NET ThreadPool. Jeśli potrzebuję wysokiej wydajności, zamierzam używać wątków i wolę używać własnej puli.
Łatwym sposobem porównania jest uruchomienie 512 wątków, 512 zadań i 512 wątków puli wątków. Na początku znajdziesz opóźnienie w Threads (stąd po co pisać pulę wątków), ale wszystkie 512 wątków będzie działać w ciągu kilku sekund, podczas gdy zadania i wątki .NET ThreadPool zajmują do kilku minut, aby rozpocząć.
Poniżej wyniki takiego testu (czterordzeniowy i5 z 16 GB RAM-u), dając każdemu 30 sekund na uruchomienie. Wykonywany kod wykonuje proste operacje we / wy pliku na dysku SSD.
Wyniki testów