Chociaż wątki mogą przyspieszyć wykonywanie kodu, czy faktycznie są potrzebne? Czy każdy fragment kodu można wykonać za pomocą jednego wątku, czy też istnieje coś, co można osiągnąć tylko za pomocą wielu wątków?
Chociaż wątki mogą przyspieszyć wykonywanie kodu, czy faktycznie są potrzebne? Czy każdy fragment kodu można wykonać za pomocą jednego wątku, czy też istnieje coś, co można osiągnąć tylko za pomocą wielu wątków?
Odpowiedzi:
Przede wszystkim wątki nie mogą przyspieszyć wykonywania kodu. Nie przyspieszają pracy komputera. Wszystko, co mogą zrobić, to zwiększyć wydajność komputera poprzez wykorzystanie czasu, który w przeciwnym razie zostałby zmarnowany. W niektórych rodzajach przetwarzania ta optymalizacja może zwiększyć wydajność i skrócić czas działania.
Prostą odpowiedzią jest tak. Możesz napisać dowolny kod do uruchomienia w jednym wątku. Dowód: system z jednym procesorem może uruchamiać instrukcje tylko liniowo. Posiadanie wielu linii wykonania jest wykonywane przez przerwania przetwarzania systemu operacyjnego, zapisywanie stanu bieżącego wątku i uruchamianie innego.
Kompleks odpowiedź brzmi ... bardziej skomplikowane! Powodem, dla którego programy wielowątkowe mogą być często bardziej wydajne niż programy liniowe, jest „problem” sprzętowy. Procesor może wykonywać obliczenia szybciej niż operacje we / wy pamięci i twardego dysku. Na przykład instrukcja „dodaj” wykonuje się znacznie szybciej niż „pobieranie”. Pamięci podręczne i pobieranie instrukcji dedykowanego programu (nie jestem pewien dokładnego terminu tutaj) może w pewnym stopniu temu przeciwdziałać, ale problem prędkości pozostaje.
Wątek jest sposobem na walkę z tym niedopasowaniem poprzez użycie procesora do instrukcji związanych z procesorem podczas wykonywania instrukcji IO. Typowy plan wykonania wątku prawdopodobnie to: Pobieranie danych, przetwarzanie danych, zapisywanie danych. Załóżmy, że pobieranie i pisanie zajmuje 3 cykle, a przetwarzanie - jeden w celach ilustracyjnych. Widzisz, że podczas gdy komputer czyta lub pisze, nic nie robi przez 2 cykle? Najwyraźniej jest leniwy i musimy złamać bicz optymalizacji!
Możemy przepisać proces za pomocą wątków, aby wykorzystać ten zmarnowany czas:
I tak dalej. Oczywiście jest to nieco wymyślony przykład, ale możesz zobaczyć, jak ta technika może wykorzystać czas, który w innym przypadku spędziłby na oczekiwaniu na zamówienie.
Należy pamiętać, że gwintowanie, jak pokazano powyżej, może zwiększyć wydajność tylko w procesach silnie związanych z operacjami wejścia / wyjścia. Jeśli program głównie oblicza rzeczy, nie będzie zbyt wielu „dziur”, w których moglibyśmy więcej pracować. Ponadto, podczas przełączania między wątkami jest narzut związany z kilkoma instrukcjami. Jeśli uruchomisz zbyt wiele wątków, procesor będzie spędzał większość czasu na przełączaniu i niewiele pracy nad problemem. Nazywa się to thrashingiem .
To wszystko jest dobre dla procesora jednordzeniowego, ale większość współczesnych procesorów ma dwa lub więcej rdzeni. Wątki nadal służą temu samemu celowi - maksymalizacji wykorzystania procesora, ale tym razem mamy możliwość uruchomienia dwóch oddzielnych instrukcji jednocześnie. Może to skrócić czas działania nawet o tyle, ile dostępnych jest rdzeni, ponieważ w rzeczywistości komputer jest wielozadaniowy, a nie przełączany kontekstowo.
W przypadku wielu rdzeni gwinty zapewniają metodę podziału pracy między dwa rdzenie. Powyższe dotyczy jednak każdego rdzenia; Program, który działa z maksymalną wydajnością z dwoma wątkami na jednym rdzeniu, najprawdopodobniej będzie działał z maksymalną wydajnością z około czterema wątkami na dwóch rdzeniach. (Wydajność mierzona jest tutaj przez wykonanie minimalnej liczby instrukcji NOP.)
Problemy z uruchomieniem wątków na wielu rdzeniach (w przeciwieństwie do jednego rdzenia) są zazwyczaj rozwiązywane przez sprzęt. Procesor upewni się, że blokuje odpowiednie lokalizacje pamięci przed odczytem / zapisem. (Czytałem, że wykorzystuje do tego specjalny bit flagi w pamięci, ale można to zrobić na kilka sposobów.) Jako programista z językami wyższego poziomu, nie musisz się martwić o nic więcej na dwóch rdzeniach, ponieważ musiałby z jednym.
TL; DR: Wątki mogą dzielić pracę, aby umożliwić komputerowi asynchroniczne przetwarzanie kilku zadań. Dzięki temu komputer może działać z maksymalną wydajnością, wykorzystując cały dostępny czas przetwarzania, zamiast blokować, gdy proces czeka na zasób.
Co może zrobić wiele wątków, czego nie może zrobić pojedynczy wątek?
Nic.
Prosty szkic próbny:
Zauważ jednak, że ukryte jest w nim wielkie założenie: język używany w jednym wątku jest kompletny w Turingu.
Bardziej interesujące pytanie brzmiałoby: „Czy dodanie tylko wielowątkowości do języka niekompletnego może sprawić, że będzie on kompletny?”. I wierzę, że odpowiedź brzmi „tak”.
Weźmy Total Functional Languages. [Dla tych, którzy nie są zaznajomieni: tak jak programowanie funkcjonalne to programowanie z funkcjami, tak samo programowanie funkcjonalne to programowanie z funkcjami całkowitymi.]
Wszystkie języki funkcjonalne oczywiście nie są kompletne w Turingu: nie można napisać nieskończonej pętli w TFPL (w rzeczywistości jest to w zasadzie definicja „całości”), ale można to zrobić w maszynie Turinga, ergo istnieje przynajmniej jeden program, który nie można zapisać w TFPL, ale można w UTM, dlatego TFPL mają mniejszą moc obliczeniową niż UTM.
Jednak gdy tylko dodasz wątki do TFPL, otrzymasz nieskończone pętle: po prostu wykonaj każdą iterację pętli w nowym wątku. Każdy pojedynczy wątek zawsze zwraca wynik, dlatego jest on Całkowity, ale każdy wątek odradza również nowy wątek, który wykonuje następną iterację, ad infinitum.
Myślę , że ten język byłby kompletny dla Turinga.
Przynajmniej odpowiada pierwotne pytanie:
Co może zrobić wiele wątków, czego nie może zrobić pojedynczy wątek?
Jeśli masz język, który nie może zrobić nieskończone pętle, następnie wielowątkowości pozwala zrobić nieskończone pętle.
Pamiętaj oczywiście, że odradzanie się wątku jest efektem ubocznym, dlatego nasz rozszerzony język nie jest już nie tylko całkowity, ale nie jest już nawet funkcjonalny.
Teoretycznie wszystko, co robi program wielowątkowy, można wykonać za pomocą programu jednowątkowego, tylko wolniej.
W praktyce różnica prędkości może być tak duża, że nie można użyć do tego celu programu jednowątkowego. Na przykład jeśli zadanie przetwarzania danych wsadowych jest uruchamiane co noc, a ukończenie jednego wątku trwa dłużej niż 24 godziny, nie ma innej opcji niż uczynienie go wielowątkowym. (W praktyce próg jest prawdopodobnie jeszcze mniejszy: często takie zadania aktualizacji muszą zakończyć się wczesnym rankiem, zanim użytkownicy zaczną ponownie korzystać z systemu. Inne zadania mogą zależeć od nich, które muszą zakończyć się również tej samej nocy. dostępny czas pracy może wynosić zaledwie kilka godzin / minut).
Wykonywanie pracy na wielu wątkach jest formą przetwarzania rozproszonego; rozpowszechniasz pracę w wielu wątkach. Innym przykładem przetwarzania rozproszonego (przy użyciu wielu komputerów zamiast wielu wątków) jest wygaszacz ekranu SETI: zebranie tak dużej ilości danych pomiarowych na jednym procesorze zajęłoby strasznie dużo czasu, a naukowcy woleliby zobaczyć wyniki przed przejściem na emeryturę ;-) nie mają budżetu na wynajem superkomputera tak długo, więc dzielą pracę na miliony komputerów domowych, aby był tani.
Chociaż wątki wydają się być niewielkim krokiem od obliczeń sekwencyjnych, w rzeczywistości stanowią ogromny krok. Odrzucają najbardziej istotne i atrakcyjne właściwości sekwencyjnego obliczania: zrozumiałość, przewidywalność i determinizm. Wątki jako model obliczeń są niezwykle niedeterministyczne, a zadaniem programisty staje się przycinanie tego niedeterminizmu.
- Problem z wątkami (www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf).
Chociaż istnieją pewne zalety w zakresie wydajności, które można uzyskać dzięki zastosowaniu wątków, które umożliwiają dystrybucję pracy na wiele rdzeni, często są one dostępne w atrakcyjnej cenie.
Jedną z wad korzystania z wątków, o których jeszcze nie wspomniano, jest utrata podziału na zasoby, który występuje w przypadku jednowątkowych przestrzeni procesów. Załóżmy na przykład, że trafiłeś na przypadek awarii. W niektórych przypadkach można to naprawić w aplikacji wieloprocesowej, po prostu pozwalając dziecku z wadą umrzeć i odrodzić nowe. Tak jest w przypadku backorków preforków Apache. Gdy jedna instancja httpd idzie w górę, najgorszym przypadkiem jest to, że konkretne żądanie HTTP może zostać odrzucone dla tego procesu, ale Apache odradza nowe dziecko, a często żądanie jest ponownie wysyłane i obsługiwane. W rezultacie Apache jako całość nie jest usuwany z wadliwym wątkiem.
Innym zagadnieniem w tym scenariuszu są wycieki pamięci. W niektórych przypadkach możesz z wdziękiem poradzić sobie z awarią wątku (w systemie UNIX odzyskiwanie po określonych sygnałach - nawet segfault / fpviolation - jest możliwe), ale nawet w takim przypadku możesz przeciekać całą pamięć przydzieloną przez ten wątek (malloc, nowy itp.). Tak więc podczas przetwarzania proces może trwać, z upływem czasu z każdą usterką / odzyskiem wycieka coraz więcej pamięci. Znów istnieją sposoby do zminimalizowania tego, takie jak użycie pul pamięci przez Apache. Ale to wciąż nie chroni przed pamięcią, która mogła zostać przydzielona przez biblioteki innych firm, których mógł używać wątek.
I, jak zauważyli niektórzy ludzie, zrozumienie prymitywów synchronizacji jest być może najtrudniejszą rzeczą, aby naprawdę wszystko naprawić. Ten problem sam w sobie - po prostu dostosowanie ogólnej logiki do całego kodu - może być ogromnym bólem głowy. Tajemnicze zakleszczenia zdarzają się w najdziwniejszych momentach, a czasem nawet nie do momentu uruchomienia programu, co utrudnia debugowanie. Dodaj do tego fakt, że operacje podstawowe synchronizacji często różnią się znacznie w zależności od platformy (Windows vs. POSIX), a debugowanie może często być trudniejsze, a także możliwość warunków wyścigu w dowolnym momencie (uruchomienie / inicjalizacja, środowisko wykonawcze i zamknięcie), programowanie z wątkami naprawdę nie ma litości dla początkujących. A nawet dla ekspertów wciąż niewiele jest litości tylko dlatego, że sama znajomość wątków nie minimalizuje w ogóle złożoności. Każda linia wątkowego kodu czasami wydaje się potęgować ogólną złożoność programu, a także zwiększa prawdopodobieństwo wystąpienia ukrytego impasu lub dziwnych warunków wyścigu w dowolnym momencie. Bardzo trudne może być także napisanie przypadków testowych w celu wykrycia tych rzeczy.
Dlatego niektóre projekty, takie jak Apache i PostgreSQL, są w większości oparte na procesach. PostgreSQL uruchamia każdy wątek zaplecza w osobnym procesie. Oczywiście to nadal nie łagodzi problemu synchronizacji i warunków wyścigu, ale dodaje sporo ochrony i w pewien sposób upraszcza.
Wiele procesów, z których każdy uruchamia jeden wątek wykonania, może być znacznie lepszych niż wiele wątków działających w jednym procesie. Wraz z pojawieniem się znacznej części nowego kodu peer-to-peer, takiego jak AMQP (RabbitMQ, Qpid itp.) I ZeroMQ, znacznie łatwiej jest dzielić wątki na różne przestrzenie procesów, a nawet maszyny i sieci, znacznie upraszczając rzeczy. Ale nadal nie jest to srebrna kula. Nadal istnieje złożoność do rozwiązania. Wystarczy przenieść niektóre zmienne z przestrzeni procesowej do sieci.
Najważniejsze jest to, że decyzja o wejściu w domenę wątków nie jest lekka. Gdy wejdziesz na to terytorium, prawie natychmiast wszystko staje się bardziej złożone i całe nowe rasy problemów wkraczają do twojego życia. Może być fajnie i fajnie, ale to jest jak energia jądrowa - gdy coś pójdzie nie tak, mogą pójść źle i szybko. Pamiętam, jak wiele lat temu brałem udział w szkoleniu z krytyczności i pokazali zdjęcia niektórych naukowców z Los Alamos, którzy bawili się plutonem w laboratoriach w czasie II wojny światowej. Wielu nie podjęło żadnych środków ostrożności w przypadku ekspozycji lub w mgnieniu oka - jednym błyskiem i bezbolesnym błyskiem wszystko się skończyło. Kilka dni później byli martwi. Richard Feynman nazwał to później „ łaskotaniem smoczego ogona„Taka może być gra z wątkami (przynajmniej dla mnie i tak). Z początku wydaje się to raczej nieszkodliwe, a kiedy gryziesz, drapiesz się po głowie, jak szybko wszystko się popsuło. Ale przynajmniej wątki wygrały cię zabić.
Po pierwsze, aplikacja jednowątkowa nigdy nie korzysta z wielordzeniowego procesora ani hiperwątkowości. Ale nawet na jednym rdzeniu procesor wielowątkowy z jednym wątkiem ma zalety.
Rozważ alternatywę i czy to cię uszczęśliwia. Załóżmy, że masz wiele zadań, które muszą być uruchamiane jednocześnie. Na przykład musisz nadal komunikować się z dwoma różnymi systemami. Jak to zrobić bez wielowątkowości? Prawdopodobnie utworzyłbyś własny harmonogram i pozwoliłby on wywoływać różne zadania, które należy wykonać. Oznacza to, że musisz podzielić swoje zadania na części. Prawdopodobnie musisz spełnić pewne ograniczenia w czasie rzeczywistym, musisz upewnić się, że części nie zajmują zbyt wiele czasu. W przeciwnym razie licznik czasu wygaśnie w innych zadaniach. To sprawia, że podział zadania jest trudniejszy. Im więcej zadań musisz samemu zarządzać, tym bardziej musisz rozdzielić i tym bardziej skomplikowany będzie twój harmonogram, aby spełnić wszystkie ograniczenia.
Gdy masz wiele wątków, życie może stać się łatwiejsze. Planista z wyprzedzeniem może zatrzymać wątek w dowolnym momencie, zachować jego stan i ponownie (uruchomić) inny. Zrestartuje się, gdy twój wątek otrzyma swoją kolej. Zalety: złożoność pisania harmonogramu została już dla Ciebie zrobiona i nie musisz dzielić swoich zadań. Ponadto harmonogram może zarządzać procesami / wątkami, o których sam nawet nie jesteś świadomy. A także, gdy wątek nic nie musi robić (czeka na jakieś zdarzenie), nie zajmie on żadnych cykli procesora. Nie jest to tak łatwe do osiągnięcia, gdy tworzysz swój niedziałający jednowątkowy harmonogram. (usypianie czegoś nie jest takie trudne, ale jak się budzi?)
Minusem wielowątkowego programowania jest to, że musisz zrozumieć kwestie współbieżności, strategii blokowania i tak dalej. Opracowanie bezbłędnego wielowątkowego kodu może być dość trudne. Debugowanie może być jeszcze trudniejsze.
czy istnieje coś, co można osiągnąć tylko za pomocą wielu wątków?
Tak. Nie można uruchamiać kodu na wielu procesorach lub rdzeniach procesorów za pomocą jednego wątku.
Bez wielu procesorów / rdzeni wątki nadal mogą uprościć kod, który koncepcyjnie działa równolegle, na przykład obsługę klienta na serwerze - ale można zrobić to samo bez wątków.
Wątki dotyczą nie tylko szybkości, ale także współbieżności.
Jeśli nie masz aplikacji wsadowej, jak sugerował @Peter, ale zamiast tego zestaw narzędzi GUI, takich jak WPF, w jaki sposób możesz komunikować się z użytkownikami i logiką biznesową za pomocą tylko jednego wątku?
Załóżmy również, że budujesz serwer WWW. Jak obsługiwałbyś więcej niż jednego użytkownika jednocześnie z jednym wątkiem (zakładając, że nie ma innych procesów)?
Istnieje wiele scenariuszy, w których wystarczy tylko jeden wątek. Właśnie dlatego dokonano najnowszych osiągnięć, takich jak procesor Intel MIC z ponad 50 rdzeniami i setkami wątków.
Tak, programowanie równoległe i współbieżne jest trudne. Ale konieczne.
Wielowątkowość pozwala interfejsowi GUI nadal reagować podczas długich operacji przetwarzania. Bez wielowątkowości użytkownik utknąłby podczas oglądania zablokowanego formularza podczas długiego procesu.
Kod wielowątkowy może zakleszczyć logikę programu i uzyskać dostęp do nieaktualnych danych w sposób niemożliwy do uzyskania przez pojedyncze wątki.
Wątki mogą pobrać niejasny błąd z czegoś, co przeciętny programista może debugować i przenieść go do królestwa, w którym opowiadane są historie o szczęściu potrzebnym do złapania tego samego błędu z opuszczonymi spodniami, gdy alarmujący programista patrzył tylko na właściwy moment.
aplikacje zajmujące się blokowaniem IO, które również muszą reagować na inne dane wejściowe (GUI lub inne połączenia), nie mogą być singleshreaded
dodanie metod sprawdzania w bibliotece IO, aby zobaczyć, ile można odczytać bez blokowania, może to pomóc, ale niewiele bibliotek daje na to pełną gwarancję
Wiele dobrych odpowiedzi, ale nie jestem pewien, czy jakieś zdanie brzmiałoby tak, jakbym to zrobił - Być może oferuje to inny sposób spojrzenia na to:
Wątki to tylko uproszczenie programowania, takie jak Objects lub Actors, lub pętle (Tak, wszystko, co implementujesz za pomocą pętli, które możesz zaimplementować za pomocą if / goto).
Bez wątków po prostu implementujesz silnik stanowy. Musiałem to robić wiele razy (za pierwszym razem, gdy to zrobiłem, nigdy o tym nie słyszałem - po prostu wydałem dużą instrukcję przełączania kontrolowaną przez zmienną „State”). Maszyny stanowe są wciąż dość powszechne, ale mogą być denerwujące. W przypadku nici ogromna część płyty kotła odchodzi.
Zdarza się również, że język jest łatwiejszy do rozbicia jego wykonywania w środowisku przyjaznym dla wielu procesorów (tak, jak sądzę, także Aktorzy).
Java zapewnia „zielone” wątki w systemach, w których system operacyjny nie zapewnia ŻADNEJ obsługi wątków. W tym przypadku łatwiej zauważyć, że są one wyraźnie niczym więcej niż abstrakcją programistyczną.
Systemy operacyjne używają koncepcji dzielenia czasu, w której każdy wątek ma czas na uruchomienie, a następnie zostaje zablokowany. Takie podejście może zastąpić wątki w obecnej formie, ale pisanie własnych harmonogramów w każdej aplikacji byłoby przesadą. Co więcej, będziesz musiał pracować z urządzeniami I / O i tak dalej. I wymagałoby wsparcia ze strony sprzętu, abyś mógł uruchamiać przerwania, aby uruchomić harmonogram. Zasadniczo pisałbyś nowy system operacyjny za każdym razem.
Ogólnie wątki mogą poprawić wydajność w przypadkach, gdy wątki czekają na We / Wy lub śpią. Pozwala także tworzyć responsywne interfejsy i umożliwia zatrzymywanie procesów podczas wykonywania długich zadań. Ponadto wątkowanie poprawia działanie prawdziwych procesorów wielordzeniowych.
Po pierwsze, wątki mogą wykonywać dwie lub więcej rzeczy jednocześnie (jeśli masz więcej niż jeden rdzeń). Chociaż możesz to zrobić z wieloma procesami, niektóre zadania po prostu nie rozkładają się zbyt dobrze na wiele procesów.
Ponadto niektóre zadania zawierają spacje, których nie można łatwo uniknąć. Na przykład trudno jest odczytać dane z pliku na dysku, a proces musi zrobić coś innego w tym samym czasie. Jeśli Twoje zadanie koniecznie wymaga dużo odczytu danych z dysku, proces poświęci dużo czasu na oczekiwanie na dysk bez względu na to, co robisz.
Po drugie, wątki pozwalają uniknąć konieczności optymalizacji dużych ilości kodu, który nie jest krytyczny dla wydajności. Jeśli masz tylko jeden wątek, każdy fragment kodu ma krytyczne znaczenie dla wydajności. Jeśli się zablokuje, jesteś zatopiony - żadne zadania, które byłyby wykonane przez ten proces, nie mogą posunąć się naprzód. W przypadku wątków blok wpływa tylko na to, że wątek i inne wątki mogą nadejść i pracować nad zadaniami, które muszą być wykonane przez ten proces.
Dobrym przykładem jest rzadko wykonywany kod obsługi błędów. Powiedzmy, że zadanie napotyka bardzo rzadki błąd, a kod do obsługi tego błędu musi przechodzić do pamięci. Jeśli dysk jest zajęty, a proces ma tylko jeden wątek, nie można wykonać postępu do przodu, dopóki kod obsługujący ten błąd nie zostanie załadowany do pamięci. Może to powodować reakcję na rozerwanie.
Innym przykładem jest sytuacja, w której bardzo rzadko konieczne jest sprawdzenie bazy danych. Jeśli zaczekasz na odpowiedź bazy danych, Twój kod uderzy w ogromne opóźnienie. Ale nie chcesz zadawać sobie trudu, aby cały ten kod był asynchroniczny, ponieważ jest tak rzadki, że musisz wykonać te wyszukiwania. Dzięki wątkowi do wykonania tej pracy uzyskasz to, co najlepsze z obu światów. Wątek umożliwiający wykonanie tej pracy sprawia, że nie ma ona decydującego znaczenia dla wydajności.