Korpusy nigdy nie odeszły, tymczasem były w cieniu innych rzeczy. Niedawno zwiększone zainteresowanie programowaniem asynchronicznym, a zatem i korporacjami, wynika w dużej mierze z trzech czynników: większej akceptacji technik programowania funkcjonalnego, zestawów narzędzi o słabym wsparciu dla prawdziwej równoległości (JavaScript! Python!), A co najważniejsze: różnych kompromisów między wątkami a korporacjami. W niektórych przypadkach użycia coroutines są obiektywnie lepsze.
Jednym z największych paradygmatów programowania lat 80., 90. i dziś jest OOP. Jeśli spojrzymy na historię OOP, a konkretnie na rozwój języka Simula, zobaczymy, że klasy wyewoluowały z koroutyn. Simula była przeznaczona do symulacji układów z dyskretnymi zdarzeniami. Każdy element systemu był osobnym procesem, który byłby wykonywany w odpowiedzi na zdarzenia na czas jednego etapu symulacji, a następnie pozwalał innym procesom wykonywać swoją pracę. Podczas opracowywania Simula 67 wprowadzono koncepcję klasy. Teraz trwały stan rogówki jest przechowywany w elementach obiektu, a zdarzenia są wywoływane przez wywołanie metody. Aby uzyskać więcej informacji, przeczytaj artykuł Rozwój języków SIMULA autorstwa Nygaard i Dahl.
Więc w zabawny sposób od zawsze używaliśmy coroutines, nazywaliśmy je po prostu obiektami i programowaniem sterowanym zdarzeniami.
W odniesieniu do paralelizmu istnieją dwa rodzaje języków: te, które mają odpowiedni model pamięci, i te, które nie mają. Model pamięci omawia takie rzeczy, jak: „Jeśli piszę do zmiennej, a następnie czytam z tej zmiennej w innym wątku, czy widzę starą wartość, nową wartość, a może nieprawidłową wartość? Co oznaczają słowa „przed” i „po”? Które operacje mają gwarancję atomowości? ”
Stworzenie dobrego modelu pamięci jest trudne, więc tego wysiłku nigdy nie podjęto w przypadku większości nieokreślonych dynamicznych języków open-source zdefiniowanych w implementacji: Perl, JavaScript, Python, Ruby, PHP. Oczywiście wszystkie te języki ewoluowały daleko poza „skrypty”, dla których zostały stworzone. Cóż, niektóre z tych języków mają jakiś dokument modelu pamięci, ale te nie są wystarczające. Zamiast tego mamy hacki:
Perl można skompilować z obsługą wątków, ale każdy wątek zawiera osobny klon pełnego stanu interpretera, co powoduje, że wątki są zbyt drogie. Jedyną korzyścią jest to, że wspólne podejście „nic” pozwala uniknąć wyścigów danych i zmusza programistów do komunikowania się tylko poprzez kolejki / sygnały / IPC. Perl nie ma silnej historii do przetwarzania asynchronicznego.
JavaScript zawsze miał bogate wsparcie dla programowania funkcjonalnego, więc programiści ręcznie kodowali kontynuacje / wywołania zwrotne w swoich programach, w których potrzebowali operacji asynchronicznych. Na przykład z żądaniami Ajax lub opóźnieniami animacji. Ponieważ sieć jest z natury asynchroniczna, istnieje wiele asynchronicznych kodów JavaScript, a zarządzanie wszystkimi tymi wywołaniami zwrotnymi jest niezwykle bolesne. Dlatego widzimy wiele wysiłków, aby lepiej zorganizować te oddzwanianie (Obietnice) lub całkowicie je wyeliminować.
Python ma tę niefortunną funkcję o nazwie Global Interpreter Lock. Zasadniczo model pamięci Python to „Wszystkie efekty pojawiają się sekwencyjnie, ponieważ nie ma równoległości. Tylko jeden wątek będzie uruchamiał kod Pythona jednocześnie. ”Tak więc, chociaż Python ma wątki, są one tak samo potężne jak coroutines. [1] Python może kodować wiele korporacji za pomocą funkcji generatora za pomocą yield
. Przy właściwym zastosowaniu może to uniknąć większości piekła zwrotnego znanego z JavaScript. Nowszy system asynchroniczny / oczekujący z Python 3.5 sprawia, że asynchroniczne idiomy są wygodniejsze w Pythonie i integruje pętlę zdarzeń.
[1]: Technicznie ograniczenia te dotyczą tylko CPython, implementacji referencyjnej Python. Inne implementacje, takie jak Jython, oferują rzeczywiste wątki, które mogą być wykonywane równolegle, ale muszą przejść wiele czasu, aby zaimplementować równoważne zachowanie. Zasadniczo: każda zmienna lub element obiektu jest zmienną lotną , dzięki czemu wszystkie zmiany są atomowe i są natychmiast widoczne we wszystkich wątkach. Oczywiście stosowanie zmiennych niestabilnych jest znacznie droższe niż stosowanie normalnych zmiennych.
Nie mam wystarczającej wiedzy na temat Ruby i PHP, aby poprawnie je upiec.
Podsumowując: niektóre z tych języków mają fundamentalne decyzje projektowe, które sprawiają, że wielowątkowość jest niepożądana lub niemożliwa, co prowadzi do większego skupienia się na alternatywach, takich jak coroutines i na sposobach uczynienia programowania asynchronicznego wygodniejszym.
Na koniec pomówmy o różnicach między coroutines a wątkami:
Wątki są w zasadzie procesami, z tym wyjątkiem, że wiele wątków w procesie współdzieli przestrzeń pamięci. Oznacza to, że nici nie są „lekkie” pod względem pamięci. Wątki są uprzednio planowane przez system operacyjny. Oznacza to, że przełączniki zadań mają duży narzut i mogą wystąpić w niewygodnych momentach. Narzut ten ma dwa składniki: koszt zawieszenia stanu wątku oraz koszt przełączania między trybem użytkownika (dla wątku) i trybem jądra (dla programu planującego).
Jeśli proces planuje swoje własne wątki bezpośrednio i kooperacyjnie, przełączanie kontekstu na tryb jądra nie jest konieczne, a przełączanie zadań jest porównywalnie kosztowne do pośredniego wywołania funkcji, jak w: dość tanie. Te lekkie nici mogą być nazywane zielonymi nitkami, włóknami lub koronami, w zależności od różnych szczegółów. Ważnymi użytkownikami zielonych nici / włókien były wczesne implementacje Java, a ostatnio Goroutines w Golang. Konceptualną zaletą coroutines jest to, że ich wykonanie można rozumieć w kategoriach przepływu kontroli wyraźnie przechodzącego w obie strony między coroutines. Jednak te korporacje nie osiągają prawdziwej równoległości, chyba że są zaplanowane w wielu wątkach systemu operacyjnego.
Gdzie przydatne są tanie kortyny? Większość oprogramowania nie potrzebuje nitek gazillionowych, więc normalne drogie nitki są zwykle OK. Jednak programowanie asynchroniczne może czasem uprościć kod. Aby móc swobodnie korzystać, ta abstrakcja musi być wystarczająco tania.
A potem jest sieć. Jak wspomniano powyżej, sieć jest z natury asynchroniczna. Żądania sieciowe po prostu zabierają dużo czasu. Wiele serwerów WWW utrzymuje pulę wątków pełną wątków roboczych. Jednak przez większość czasu wątki te pozostają na biegu jałowym, ponieważ czekają na jakiś zasób, czy to na zdarzenie we / wy podczas ładowania pliku z dysku, czekając, aż klient potwierdzi część odpowiedzi, czy czekając na bazę danych zapytanie zostało zakończone. NodeJS fenomenalnie wykazał, że konsekwentny i oparty na zdarzeniach asynchroniczny projekt serwera działa wyjątkowo dobrze. Oczywiście JavaScript jest daleki od jedynego języka używanego w aplikacjach internetowych, dlatego istnieje duża zachęta dla innych języków (zauważalnych w Pythonie i C #), aby ułatwić asynchroniczne programowanie w Internecie.