Kiedy potrzebujesz „setek tysięcy” wątków?

31

Erlang, Go i Rust twierdzą, że w taki czy inny sposób wspierają programowanie równoległe za pomocą tanich „wątków” / coroutines. The Go FAQ stany:

Praktyczne jest tworzenie setek tysięcy goroutyn w tej samej przestrzeni adresowej.

Rust Tutorial mówi:

Ponieważ zadania są znacznie tańsze niż tradycyjne wątki, Rust może tworzyć setki tysięcy współbieżnych zadań w typowym systemie 32-bitowym.

Dokumentacja Erlanga mówi:

Domyślny początkowy rozmiar sterty wynoszący 233 słowa jest dość konserwatywny, aby obsługiwać systemy Erlang z setkami tysięcy, a nawet milionami procesów.

Moje pytanie: jaki rodzaj aplikacji wymaga tak wielu jednoczesnych wątków wykonania? Tylko najbardziej obciążone serwery WWW przyjmują nawet tysiące jednocześnie odwiedzających. Aplikacje typu szef-pracownik / dyspozytor napisałem, że trafienia maleją, gdy liczba wątków / procesów jest znacznie większa niż liczba rdzeni fizycznych. Podejrzewam, że może to mieć sens w zastosowaniach numerycznych, ale w rzeczywistości większość ludzi przekazuje równoległość do bibliotek stron trzecich napisanych w Fortran / C / C ++, a nie w językach nowej generacji.

użytkownik39019
źródło
5
Myślę, że źródłem twojego pomieszania jest to, że te mikroprzecieki / zadania / itp. Nie są przede wszystkim przeznaczone jako substytut wątków / procesów systemu operacyjnego, o których mówisz, ani też nie są przeznaczone do dzielenia dużej, możliwej do równoległego, dużej części dzielenia liczb pomiędzy kilkoma rdzeniami (jak słusznie zauważyłeś, nie ma sensu mieć 100k wątków na 4 rdzeniach w tym celu).
us2012
1
Więc do czego są przeznaczone? Może jestem naiwny, ale nigdy nie spotkałem się z sytuacją, w której wprowadzenie coroutines / etc uprościłoby program do wykonywania pojedynczych wątków. I udało mi się osiągnąć „niski” poziom współbieżności z procesami, które w Linuksie mogę uruchomić setki lub tysiące bez zerwania.
user39019,
Nie ma sensu, aby tak wiele zadań faktycznie działało. To nie znaczy, że nie mogłeś mieć dużej liczby zadań, które były po prostu zablokowane, czekając, aż coś się wydarzy.
Loren Pechtel,
5
Ideą asynchronii zadaniowej a asynchronicznej opartej na wątkach jest stwierdzenie, że kod użytkownika powinien koncentrować się na zadaniach, które muszą się zdarzyć, a nie zarządzać pracownikami wykonującymi te zadania. Pomyśl o wątku jako o pracowniku, którego zatrudniasz; zatrudnienie pracownika jest drogie, a jeśli tak, to chcesz, aby ciężko pracował nad tyloma zadaniami, jak to możliwe, przez 100% czasu. Wiele systemów można scharakteryzować jako posiadających setki lub tysiące oczekujących zadań, ale nie potrzebujesz setek lub tysięcy pracowników.
Eric Lippert,
Kontynuując komentarz @ EricLippert, istnieje kilka sytuacji, w których istniałyby setki tysięcy zadań. Przykład 1: rozkład zadania równoległego do danych, takiego jak przetwarzanie obrazu. Przykład 2: serwer obsługujący setki tysięcy klientów, z których każdy może potencjalnie wydać polecenie w dowolnym momencie. Każde zadanie wymagałoby własnego „lekkiego kontekstu wykonania” - zdolności do zapamiętania stanu, w jakim się znajduje (protokoły komunikacyjne), polecenia, które aktualnie wykonuje, i niewiele więcej. Lekkość jest możliwa, o ile każdy ma płytki stos wywołań.
rwong

Odpowiedzi:

19

jeden przypadek użycia - websockets:
ponieważ websockets są długotrwałe w porównaniu do prostych żądań, na zajętym serwerze z czasem gromadzi się wiele websockets. microthreads zapewniają dobre modelowanie koncepcyjne, a także stosunkowo łatwą implementację.

bardziej ogólnie, przypadki, w których wiele mniej lub bardziej autonomicznych jednostek czeka na pewne zdarzenia, powinny być dobrymi przypadkami użycia.

kr1
źródło
15

Pomóc może zastanowienie się nad tym, do czego pierwotnie miał służyć Erlang, którym było zarządzanie telekomunikacją. Czynności takie jak routing, przełączanie, zbieranie / agregacja czujników itp.

Przenosząc to do świata internetowego - rozważ system taki jak Twitter . System prawdopodobnie nie użyłby mikroprzeczyń do generowania stron internetowych, ale mógłby użyć ich do gromadzenia / buforowania / dystrybucji tweetów.

Ten artykuł może być pomocny.

Dave Clausen
źródło
11

W języku, w którym nie wolno modyfikować zmiennych, prosty czynność utrzymywania stanu wymaga osobnego kontekstu wykonania (który większość osób nazwałaby wątkiem, a Erlang - procesem). Zasadniczo wszystko jest pracownikiem.

Rozważ tę funkcję Erlang, która utrzymuje licznik:

counter(Value) ->
    receive                               % Sit idle until a message is received
        increment -> counter(Value + 1);  % Restart with incremented value
        decrement -> counter(Value - 1);  % Restart with decremented value
        speak     ->
            io:fwrite("~B~n", [Value]),
            counter(Value);               % Restart with unaltered value
        _         -> counter(Value)       % Anything else?  Do nothing.
    end.

W tradycyjnym języku OO, takim jak C ++ lub Java, można to osiągnąć, mając klasę z prywatnym członkiem klasy, publiczne metody uzyskania lub zmiany jego stanu oraz obiekt instancji dla każdego licznika. Erlang zastępuje pojęcie instancji obiektu procesem, pojęcie metod komunikatami, a utrzymanie stanu wywołaniami tail, które restartują funkcję z dowolnymi wartościami tworzącymi nowy stan. Niewidoczną zaletą tego modelu - i większości racji bytu Erlanga - jest to, że język automatycznie serializuje dostęp do wartości licznika za pomocą kolejki komunikatów, co sprawia, że ​​współbieżny kod jest bardzo łatwy do wdrożenia z wysokim poziomem bezpieczeństwa .

Prawdopodobnie przyzwyczaiłeś się do tego, że przełączniki kontekstu są drogie, co nadal jest prawdą z punktu widzenia systemu operacyjnego hosta. Środowisko wykonawcze Erlang samo w sobie jest małym, dostrojonym systemem operacyjnym, więc przełączanie między własnymi procesami jest szybkie i wydajne, a wszystko to przy minimalnym ograniczeniu liczby przełączeń kontekstu. Z tego powodu posiadanie wielu tysięcy procesów nie stanowi problemu i jest zalecane.

Blrfl
źródło
1
Twoja ostatnia aplikacja counter/1powinna używać małej litery c;) Próbowałem to naprawić, ale StackExchange nie lubi edycji 1-znakowej.
d11wtq
4

Moje pytanie: jaki rodzaj aplikacji wymaga tak wielu jednoczesnych wątków wykonania?

1) Fakt, że język „skaluje się” oznacza, że ​​istnieje mniejsze prawdopodobieństwo, że będziesz musiał rzucić ten język, gdy sytuacja stanie się bardziej skomplikowana. (Jest to tak zwana koncepcja „całego produktu”). Wiele osób porzuca Apache dla Nginx z tego właśnie powodu. Jeśli zbliżysz się do „twardego limitu” narzuconego przez nadmiar wątku, przestraszysz się i zaczniesz zastanawiać się, jak go ominąć. Strony internetowe nigdy nie są w stanie przewidzieć, jaki ruch uzyskają, więc rozsądne jest poświęcenie trochę czasu na skalowanie.

2) Jeden goroutine na żądanie to dopiero początek. Istnieje wiele powodów, aby używać goroutines wewnętrznie.

  • Rozważ aplikację internetową z równoczesnymi żądaniami 100, ale każde żądanie generuje 100 żądań zaplecza. Oczywistym przykładem jest agregator wyszukiwarek. Ale prawie każda aplikacja może tworzyć goroutiny dla każdego „obszaru” na ekranie, a następnie generować je niezależnie zamiast sekwencyjnie. Na przykład każda strona w Amazon.com składa się z ponad 150 żądań zaplecza, przygotowanych specjalnie dla Ciebie. Nie zauważasz, ponieważ są one równoległe, a nie sekwencyjne, a każdy „obszar” to jego własny serwis internetowy.
  • Zastanów się nad każdą aplikacją, w której niezawodność i opóźnienia są najważniejsze. Prawdopodobnie chcesz, aby każde przychodzące żądanie uruchomiło kilka żądań zaplecza i zwróciło dane, które pojawią się jako pierwsze .
  • Rozważ każde „dołączenie do klienta” wykonane w Twojej aplikacji. Zamiast mówić „dla każdego elementu, uzyskaj dane”, możesz wydzielić garść goroutine. Jeśli masz kilka podrzędnych baz danych do zapytania, magicznie pójdziesz N czas szybciej. Jeśli tego nie zrobisz, nie będzie wolniej.

trafienia maleją, gdy liczba wątków / procesów jest znacznie większa niż liczba rdzeni fizycznych

Wydajność nie jest jedynym powodem podziału programu na CSP . W rzeczywistości może to ułatwić zrozumienie programu, a niektóre problemy można rozwiązać za pomocą znacznie mniejszej ilości kodu.

Tak jak na pokazanych powyżej slajdach, współbieżność w kodzie jest sposobem na uporządkowanie problemu. Brak goroutin jest jak brak struktury danych Map / Dictonary / Hash w twoim języku. Bez tego możesz sobie poradzić. Ale kiedy już go masz, zaczynasz go używać wszędzie i to naprawdę upraszcza twój program.

W przeszłości oznaczało to „rozwijanie własnego” programowania wielowątkowego. Ale to było skomplikowane i niebezpieczne - wciąż nie ma wielu narzędzi, aby upewnić się, że nie tworzysz ras. Jak zapobiegać błędom przyszłego opiekuna? Jeśli spojrzysz na duże / złożone programy, zobaczysz, że wydają one dużo zasobów w tym kierunku.

Ponieważ współbieżność nie jest pierwszorzędną częścią większości języków, dzisiejsi programiści nie dostrzegają, dlaczego byłoby to dla nich przydatne. Stanie się to bardziej widoczne, gdy każdy telefon i zegarek zbliżą się do 1000 rdzeni. Idź na statki z wbudowanym narzędziem do wykrywania wyścigów.

BraveNewCurrency
źródło
2

W przypadku Erlang powszechny jest jeden proces na połączenie lub inne zadanie. Na przykład serwer przesyłania strumieniowego audio może mieć 1 proces na podłączonego użytkownika.

Erlang VM jest zoptymalizowany do obsługi tysięcy, a nawet setek tysięcy procesów, dzięki czemu przełączniki kontekstu są bardzo tanie.

Zachary K.
źródło
1

Wygoda. Kiedy zacząłem programować wielowątkowo, dla zabawy przeprowadzałem wiele symulacji i tworzenia gier. Przekonałem się, że bardzo wygodnym rozwiązaniem jest po prostu odkręcenie nici dla każdego obiektu i pozwolenie mu robić to samo, zamiast przetwarzać każdy z nich przez pętlę. Jeśli twój kod nie jest zakłócany przez niedeterministyczne zachowanie i nie masz kolizji, może to ułatwić kodowanie. Mając dostępną teraz moc, gdybym wrócił do tego, z łatwością wyobrażam sobie wyplątanie kilku tysięcy wątków z powodu wystarczającej mocy przetwarzania i pamięci do obsługi tak wielu dyskretnych obiektów!

Brian Knoblauch
źródło
1

Prosty przykład Erlanga, który został zaprojektowany do komunikacji: przesyłanie pakietów sieciowych. Gdy wykonasz jedno żądanie http, możesz mieć tysiące pakietów TCP / IP. Dodaj do tego, że wszyscy łączą się w tym samym czasie, a masz swój przypadek użycia.

Rozważ wiele aplikacji używanych wewnętrznie przez dowolną dużą firmę do obsługi zamówień lub czegokolwiek, czego mogą potrzebować. Serwery WWW nie są jedyną rzeczą wymagającą wątków.

Florian Margaine
źródło
-2

Przypomina mi się niektóre zadania renderowania. Jeśli wykonujesz długi łańcuch operacji na każdym pikselu obrazu, a jeśli te operacje są możliwe do zrównoleglenia, to nawet stosunkowo niewielki obraz 1024 x 768 mieści się w nawiasie „setek tysięcy”.

Maximus Minimus
źródło
2
Kilka lat temu spędziłem kilka lat na przetwarzaniu obrazu FLIR w czasie rzeczywistym, przeglądając 256 x 256 obrazów z prędkością 30 klatek na sekundę. O ile nie masz DUŻO procesorów SPRZĘTOWYCH i BEZSZWOWY sposób partycjonowania danych między nimi, OSTATNIE, co chcesz zrobić, to dodać przełączanie kontekstu, rywalizację o pamięć i przerzucanie pamięci podręcznej do rzeczywistych kosztów obliczeniowych.
John R. Strohm,
To zależy od wykonywanej pracy. Jeśli wszystko, co robisz, to przekazanie zadania do sprzętowego rdzenia / jednostki wykonawczej, po czym możesz skutecznie o nim zapomnieć (i zauważyć, że tak działają procesory graficzne, więc nie jest to hipotetyczny scenariusz), wówczas podejście jest następujące ważny.
Maximus Minimus