Czym się różni „coroutine” od „wątku”?

Odpowiedzi:

122

Korekty są formą przetwarzania sekwencyjnego: tylko jeden jest wykonywany w danym momencie (podobnie jak podprogramy AKA funkcje AKA - po prostu przekazują sobie pałeczkę bardziej płynnie).

Wątki są (przynajmniej koncepcyjnie) formą przetwarzania współbieżnego: w danym momencie może być wykonywanych wiele wątków. (Tradycyjnie na maszynach jednordzeniowych z jednym procesorem, ta współbieżność była symulowana z pewną pomocą systemu operacyjnego - obecnie, ponieważ tak wiele maszyn jest wieloprocesorowych i / lub wielordzeniowych, wątki będą de facto wykonywać jednocześnie nie tylko „koncepcyjnie”).

Alex Martelli
źródło
188

Pierwsze przeczytanie: Współbieżność a równoległość - jaka jest różnica?

Współbieżność to oddzielenie zadań w celu zapewnienia wykonywania z przeplotem. Równoległość to jednoczesne wykonywanie wielu prac w celu zwiększenia szybkości. - https://github.com/servo/servo/wiki/Design

Krótka odpowiedź: W przypadku wątków system operacyjny przełącza uruchomione wątki zapobiegawczo zgodnie z jego harmonogramem, który jest algorytmem w jądrze systemu operacyjnego. W przypadku programów programista i język programowania określają, kiedy przełączać programy; innymi słowy, zadania są wspólnie wielozadaniowe poprzez wstrzymywanie i wznawianie funkcji w ustalonych punktach, zazwyczaj (ale niekoniecznie) w ramach jednego wątku.

Długa odpowiedź: W przeciwieństwie do wątków, które są planowane z wyprzedzeniem przez system operacyjny, standardowe przełączniki współpracują, co oznacza, że ​​programista (i być może język programowania i jego środowisko wykonawcze) kontroluje, kiedy nastąpi przełączenie.

W przeciwieństwie do wątków, które są wywłaszczające, standardowe przełączniki współpracują (programista kontroluje, kiedy nastąpi przełączenie). Jądro nie bierze udziału w standardowych przełącznikach. - http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/coroutine/overview.html

Język obsługujący wątki natywne może wykonywać swoje wątki (wątki użytkownika) na wątkach systemu operacyjnego ( wątkach jądra ). Każdy proces ma co najmniej jeden wątek jądra. Wątki jądra są podobne do procesów, z tą różnicą, że współużytkują pamięć w procesie będącym właścicielem ze wszystkimi innymi wątkami w tym procesie. Proces „posiada” wszystkie przypisane mu zasoby, takie jak pamięć, uchwyty plików, gniazda, uchwyty urządzeń itp., A wszystkie te zasoby są współdzielone między jego wątkami jądra.

Harmonogram systemu operacyjnego jest częścią jądra, która uruchamia każdy wątek przez określony czas (na komputerze z jednym procesorem). Program planujący przydziela czas (podział czasu) do każdego wątku, a jeśli wątek nie zostanie zakończony w tym czasie, harmonogram wyprzedza go (przerywa go i przełącza do innego wątku). Wiele wątków może działać równolegle na maszynie wieloprocesorowej, ponieważ każdy wątek może być (ale niekoniecznie musi) być zaplanowany na oddzielny procesor.

Na komputerze jednoprocesorowym wątki są szybko dzielone czasowo i wywłaszczane (przełączane) (w Linuksie domyślny czas to 100 ms), co czyni je współbieżnymi. Nie można ich jednak uruchamiać równolegle (jednocześnie), ponieważ procesor jednordzeniowy może uruchamiać tylko jedną rzecz naraz.

Do zaimplementowania funkcji kooperacyjnych można użyć programów i / lub generatorów . Zamiast być uruchamiane w wątkach jądra i planowane przez system operacyjny, działają one w jednym wątku, dopóki nie ustąpią lub nie skończą, dając inne funkcje określone przez programistę. Języki z generatorami , takie jak Python i ECMAScript 6, mogą być używane do tworzenia programów. Async / await (widoczne w C #, Pythonie, ECMAscript 7, Rust) to abstrakcja zbudowana na podstawie funkcji generatora, które dają kontrakty futures / obietnice.

W niektórych kontekstach coroutines mogą odnosić się do funkcji stosowych, podczas gdy generatory mogą odnosić się do funkcji bez stosu.

Włókna , lekkie nici i zielone nici to inne nazwy rutynów lub rzeczy podobnych do rutynów. Czasami mogą wyglądać (zazwyczaj celowo) bardziej jak wątki systemu operacyjnego w języku programowania, ale nie działają równolegle jak rzeczywiste wątki i zamiast tego działają jak programy korygujące. (Mogą istnieć bardziej szczegółowe techniczne szczegóły lub różnice między tymi koncepcjami w zależności od języka lub implementacji).

Na przykład Java ma „ zielone wątki ”; były to wątki zaplanowane przez wirtualną maszynę języka Java (JVM) zamiast natywnie w wątkach jądra bazowego systemu operacyjnego. Nie działały one równolegle ani nie korzystały z wielu procesorów / rdzeni - ponieważ wymagałoby to natywnego wątku! Ponieważ nie były zaplanowane przez system operacyjny, przypominały bardziej programy niż wątki jądra. Zielone wątki są tym, czego Java używała, dopóki natywne wątki nie zostały wprowadzone do Java 1.2.

Wątki zużywają zasoby. W JVM każdy wątek ma swój własny stos, zwykle o rozmiarze 1 MB. 64 KB to najmniejsza ilość miejsca na stosie dozwolona na wątek w JVM. Rozmiar stosu wątków można skonfigurować w wierszu komend maszyny JVM. Pomimo nazwy, wątki nie są darmowe, ze względu na wykorzystanie zasobów, takich jak każdy wątek, który potrzebuje własnego stosu, lokalnego magazynu wątków (jeśli istnieje) oraz kosztów planowania wątków / przełączania kontekstu / unieważniania pamięci podręcznej procesora. Jest to jeden z powodów, dla których programy Coroutines stały się popularne w aplikacjach o krytycznym znaczeniu dla wydajności, wysoce współbieżnych.

Mac OS pozwoli tylko procesowi przydzielić około 2000 wątków, a Linux przydzieli 8 MB stosu na wątek i zezwoli tylko na tyle wątków, ile zmieści się w fizycznej pamięci RAM.

W związku z tym wątki mają największą wagę (pod względem wykorzystania pamięci i czasu przełączania kontekstów), następnie programy korygujące, a na koniec generatory są najlżejsze.

llambda
źródło
2
+1, ale ta odpowiedź może skorzystać z niektórych odniesień.
kojiro
1
Zielone nici to coś innego niż coroutines. czyż nie? Nawet włókna mają pewne różnice. patrz programmers.stackexchange.com/questions/254140/ ...
113

Około 7 lat później, ale odpowiedzi tutaj nie zawierają kontekstu dotyczącego współprogramów i wątków. Dlaczego ostatnio tak wiele uwagi poświęca się programom i kiedy powinienem ich używać w porównaniu z wątkami ?

Po pierwsze, jeśli programy działają równolegle (nigdy równolegle ), dlaczego ktoś miałby je preferować od wątków?

Odpowiedź jest taka, że ​​koreprogramy mogą zapewnić bardzo wysoki poziom współbieżności przy bardzo niewielkim narzutie . Ogólnie w środowisku wątkowym masz co najwyżej 30–50 wątków, zanim ilość zmarnowanego narzutu w rzeczywistości planowania tych wątków (przez harmonogram systemowy) znacznie skróci czas, w którym wątki faktycznie wykonują użyteczną pracę.

Ok, więc w przypadku wątków możesz mieć równoległość, ale niezbyt dużą równoległość, czy to nie jest lepsze niż procedura równoległa działająca w jednym wątku? Cóż, niekoniecznie. Pamiętaj, że procedura współbieżna może nadal wykonywać współbieżność bez obciążenia harmonogramu - po prostu zarządza przełączaniem kontekstu.

Na przykład, jeśli masz procedurę wykonującą jakąś pracę i wykonuje ona operację, o której wiesz, że będzie blokowana przez pewien czas (np. Żądanie sieciowe), dzięki procedurze równoległej możesz natychmiast przełączyć się na inną procedurę bez narzutu związanego z włączaniem harmonogramu systemu do ta decyzja - tak, programista musi określić, kiedy współprogramy mogą się przełączać.

Ponieważ wiele procedur wykonuje bardzo małe fragmenty pracy i dobrowolnie przełącza się między sobą, osiągnąłeś poziom wydajności, na osiągnięcie którego żaden planista nie mógł mieć nadziei. Teraz możesz mieć tysiące koreprogramów pracujących razem w przeciwieństwie do dziesiątek wątków.

Ponieważ twoje procedury teraz przełączają się między sobą w ustalonych wcześniej punktach, możesz teraz również uniknąć blokowania współdzielonych struktur danych (ponieważ nigdy nie powiedziałbyś swojemu kodowi, aby przełączył się na inny program w środku krytycznej sekcji)

Kolejną korzyścią jest znacznie mniejsze zużycie pamięci. W modelu wątkowym każdy wątek musi przydzielić swój własny stos, więc użycie pamięci rośnie liniowo wraz z liczbą posiadanych wątków. W przypadku procedur równoległych liczba dostępnych procedur nie ma bezpośredniego związku z wykorzystaniem pamięci.

I wreszcie, wiele uwagi poświęca się współprogramom, ponieważ w niektórych językach programowania (takich jak Python) Twoje wątki i tak nie mogą działać równolegle - działają równolegle, tak jak procedury, ale bez małej ilości pamięci i wolnego narzutu planowania.

Martin Konecny
źródło
2
Jak przejść do innego zadania w programach, gdy napotkamy operację blokującą?
Narcisse Doudieu Siewe
Sposób przełączania się do innego zadania polega na tym, że każda operacja blokowania jest faktycznie wykonywana asynchronicznie. Oznacza to, że musisz unikać wykonywania jakichkolwiek operacji, które faktycznie blokowałyby, i używać tylko operacji, które obsługują brak blokowania, gdy są używane w twoim systemie coroutine. Jedynym sposobem obejścia tego jest posiadanie programów obsługiwanych przez jądro, takich jak na przykład UMS w systemie Windows, w których skacze do harmonogramu za każdym razem, gdy "wątek" UMS blokuje wywołanie systemowe.
retep998
@MartinKonecny ​​Czy najnowsze C ++ Threads TS jest zgodne z podejściem, o którym wspomniałeś?
Nikos,
Ostatecznie więc nowoczesny język programowania będzie potrzebował obu Coroutines / Fibers, aby efektywnie wykorzystać pojedynczy rdzeń procesora np. Do operacji nieobciążających obliczeniami, takich jak operacje we / wy i wątki w celu zrównoleglenia operacji intensywnie korzystających z procesora na wielu rdzeniach w celu zwiększenia szybkości, prawda?
Mahatma_Fatal_Error
19

Jednym słowem: wyprzedzanie. Programy działają jak żonglerzy, którzy przekazują sobie nawzajem dobrze wyćwiczone punkty. Wątki (prawdziwe wątki) mogą zostać przerwane w prawie każdym momencie, a następnie wznowione później. Oczywiście wiąże się to z różnego rodzaju konfliktami zasobów, stąd niesławny GIL - Global Interpreter Lock w Pythonie.

Wiele implementacji wątków jest w rzeczywistości bardziej podobnych do coroutines.

Peter Rowell
źródło
9

To zależy od języka, którego używasz. Na przykład w Lua są tym samym (nazywany jest zmiennym typem programu thread).

Zwykle jednak programy realizują dobrowolne ustępstwa, gdzie (ty) programista decydujesz, gdzie yield, tj. Przekazać kontrolę innej procedurze.

Zamiast tego wątki są automatycznie zarządzane (zatrzymywane i uruchamiane) przez system operacyjny i mogą nawet działać w tym samym czasie na procesorach wielordzeniowych.

Thomas Bonini
źródło
0

12 lat spóźniłem się do dyskusji, ale w nazwie wyjaśnienie ma korektor. Coroutine można rozłożyć na Co i Routine.

Procedura w tym kontekście jest po prostu sekwencją operacji / działań, a przez wykonanie / przetwarzanie procedury sekwencja operacji jest wykonywana jedna po drugiej w dokładnie takiej samej kolejności, jak określono.

Co oznacza współpracę. Procedura towarzysząca jest proszona (lub lepiej oczekiwana) o dobrowolne zawieszenie jej wykonywania, aby dać szansę na wykonanie również innym procedurom towarzyszącym. Zatem procedura równoległa polega na współużytkowaniu zasobów procesora (dobrowolnie), aby inni mogli korzystać z tych samych zasobów, z których korzystasz.

Z drugiej strony wątek nie musi wstrzymywać wykonania. Zawieszenie jest całkowicie przezroczyste dla nici, a nić jest zmuszana przez znajdujący się pod nią sprzęt do zawieszenia się. Odbywa się to również w taki sposób, że jest przeważnie przezroczysty dla wątku, ponieważ nie jest powiadamiany, a jego stan nie jest zmieniany, ale zapisywany i później przywracany, gdy wątek może kontynuować.

Jedna rzecz nie jest prawdą, że współprogramy nie mogą być wykonywane jednocześnie i nie mogą wystąpić warunki wyścigu. Zależy to od systemu, w którym działają procedury równoległe, i łatwo jest je obrazować.

Nie ma znaczenia, w jaki sposób procedury towarzyszące same się zawieszają. W Windows 3.1 int 03 został wpleciony w dowolne programy (lub musiał być tam umieszczony), aw C # dodajemy wydajność.

Martin Kersten
źródło