Coroutine

Od 2003 roku wiele z najpopularniejszych języków programowania, w tym C i jego pochodne, nie posiada bezpośredniego wsparcia dla coroutines w ramach języka lub ich standardowych bibliotek. Wynika to, w dużej mierze, z ograniczeń implementacji podprogramów opartych na stosie. Wyjątkiem jest biblioteka C++ Boost.Context, część bibliotek boost, która wspiera zamianę kontekstu na ARM, MIPS, PowerPC, SPARC i x86 w POSIX, Mac OS X i Windows. Korutyny mogą być budowane na Boost.Context.

W sytuacjach, w których korutyna byłaby naturalną implementacją mechanizmu, ale nie jest dostępna, typową odpowiedzią jest użycie zamknięcia – podprogramu ze zmiennymi stanu (zmienne statyczne, często flagi boolean), aby utrzymać wewnętrzny stan między wywołaniami i przekazać kontrolę do właściwego punktu. Warunkowania w kodzie powodują wykonanie różnych ścieżek kodu przy kolejnych wywołaniach, w zależności od wartości zmiennych stanu. Inną typową odpowiedzią jest implementacja jawnej maszyny stanów w postaci dużej i złożonej instrukcji switch lub poprzez instrukcję goto, w szczególności obliczone goto. Takie implementacje są uważane za trudne do zrozumienia i utrzymania, i stanowią motywację dla wsparcia coroutine.

Wątki, i w mniejszym stopniu włókna, są alternatywą dla coroutines w głównym nurcie środowisk programistycznych dzisiaj. Wątki dostarczają udogodnień do zarządzania interakcją współpracy w czasie rzeczywistym jednocześnie wykonujących się fragmentów kodu. Wątki są szeroko dostępne w środowiskach, które wspierają C (i są wspierane natywnie w wielu innych nowoczesnych językach), są znane wielu programistom i zazwyczaj są dobrze zaimplementowane, dobrze udokumentowane i dobrze wspierane. Jednakże, ponieważ rozwiązują one duży i trudny problem, zawierają wiele potężnych i złożonych obiektów i mają odpowiednio trudną krzywą uczenia się. Jako takie, gdy coroutine jest wszystkim, co jest potrzebne, użycie wątku może być overkill.

Jedną z ważnych różnic między wątkami i coroutines jest to, że wątki są zwykle preemptively zaplanowane, podczas gdy coroutines nie są. Ponieważ wątki mogą być ponownie zaplanowane w każdej chwili i mogą wykonywać się współbieżnie, programy używające wątków muszą uważać na blokowanie. W przeciwieństwie do tego, ponieważ coroutines mogą być zmieniane tylko w określonych punktach programu i nie wykonują się współbieżnie, programy używające coroutines mogą często całkowicie uniknąć blokowania. Właściwość ta jest również wymieniana jako zaleta programowania sterowanego zdarzeniami lub asynchronicznego.

Ponieważ fibery są wspólnie zaplanowane, stanowią one idealną bazę do implementacji powyższych coroutines. Jednakże, wsparcie systemowe dla włókien jest często niewystarczające w porównaniu z tym dla wątków.

Implementacje dla CEdit

Aby zaimplementować coroutines ogólnego przeznaczenia, należy uzyskać drugi stos wywołań, co jest cechą nie obsługiwaną bezpośrednio przez język C. Niezawodnym (aczkolwiek specyficznym dla platformy) sposobem na osiągnięcie tego jest użycie niewielkiej ilości asemblacji inline do jawnego manipulowania wskaźnikiem stosu podczas początkowego tworzenia coroutine. Jest to podejście zalecane przez Toma Duffa w dyskusji na temat jego względnych zalet w porównaniu do metody używanej przez Protothreads. Na platformach, które udostępniają wywołanie systemowe POSIX sigaltstack, drugi stos wywołań może być uzyskany przez wywołanie funkcji springboard z wnętrza handler’a sygnału, aby osiągnąć ten sam cel w przenośnym C, kosztem pewnej dodatkowej złożoności. Biblioteki C zgodne z POSIX lub Single Unix Specification (SUSv3) udostępniały takie procedury jak getcontext, setcontext, makecontext i swapcontext, ale funkcje te zostały uznane za przestarzałe w POSIX 1.2008.

Po uzyskaniu drugiego stosu wywołań za pomocą jednej z wyżej wymienionych metod, funkcje setjmp i longjmp w standardowej bibliotece C mogą być następnie użyte do implementacji przełączników między coroutines. Funkcje te zapisują i przywracają, odpowiednio, wskaźnik stosu, licznik programu, rejestry zapisane w wywołaniach i wszelkie inne wewnętrzne stany wymagane przez ABI, tak że powrót do korutyny po jej wykonaniu przywraca wszystkie stany, które zostałyby przywrócone po powrocie z wywołania funkcji. Minimalistyczne implementacje, które nie korzystają z funkcji setjmp i longjmp, mogą osiągnąć ten sam rezultat za pomocą małego bloku asemblacji inline, który zamienia tylko wskaźnik stosu i licznik programu, a wszystkie inne rejestry usuwa. Może to być znacznie szybsze, ponieważ setjmp i longjmp muszą konserwatywnie przechowywać wszystkie rejestry, które mogą być w użyciu zgodnie z ABI, podczas gdy metoda clobber pozwala kompilatorowi przechowywać (przez wylanie na stos) tylko to, co wie, że jest rzeczywiście w użyciu.

Z powodu braku bezpośredniego wsparcia językowego, wielu autorów napisało własne biblioteki dla coroutines, które ukrywają powyższe szczegóły. Biblioteka libtask Russa Coxa jest dobrym przykładem tego gatunku. Używa ona funkcji kontekstowych, jeśli są one dostarczane przez natywną bibliotekę C; w przeciwnym razie dostarcza własne implementacje dla ARM, PowerPC, Sparc i x86. Inne godne uwagi implementacje obejmują libpcl, coro, lthread, libCoroutine, libconcurrency, libcoro, ribs2, libdill, libaco, i libco.

Oprócz powyższego ogólnego podejścia, podjęto kilka prób przybliżenia coroutines w C za pomocą kombinacji podprogramów i makr. Wkład Simona Tathama, oparty na urządzeniu Duffa, jest godnym uwagi przykładem gatunku i stanowi podstawę dla Protothreads i podobnych implementacji. Oprócz zastrzeżeń Duffa, własne komentarze Tathama dostarczają szczerej oceny ograniczeń tego podejścia: „O ile mi wiadomo, jest to najgorszy kawałek hackery C, jaki kiedykolwiek widziano w poważnym kodzie produkcyjnym”. Główne wady tego przybliżenia polegają na tym, że nie utrzymując oddzielnej ramki stosu dla każdej coroutine, zmienne lokalne nie są zachowywane przez wyjścia z funkcji, nie jest możliwe posiadanie wielu wejść do funkcji, a sterowanie może być przekazywane tylko z procedury najwyższego poziomu.

Implementacje dla C++Edit

  • C++ coroutines TS (Technical Specification), standard dla rozszerzeń języka C++ dla pozbawionego stosu podzbioru zachowania podobnego do coroutine, jest w trakcie opracowywania. Visual C++ i Clang już wspierają główne fragmenty w przestrzeni nazw std::experimental. coroutines Technical Specification
  • Boost.Coroutine – stworzona przez Olivera Kowalke, jest oficjalnie wydaną przenośną biblioteką coroutine boost od wersji 1.53. Biblioteka opiera się na Boost.Context i obsługuje ARM, MIPS, PowerPC, SPARC i X86 na POSIX, Mac OS X i Windows.
  • Boost.Coroutine2 – również stworzona przez Olivera Kowalke, jest zmodernizowaną przenośną biblioteką coroutine od boost w wersji 1.59. Wykorzystuje cechy C++11, ale usuwa wsparcie dla symetrycznych coroutines.
  • Mordor – w 2010 roku Mozy otworzył bibliotekę C++ implementującą coroutines, z naciskiem na wykorzystanie ich do abstrakcji asynchronicznego I/O do bardziej znanego modelu sekwencyjnego.
  • CO2 – coroutine bez stosu, oparta na sztuczkach preprocesora C++, zapewniająca emulację await/yield.
  • ScummVM – Projekt ScummVM implementuje lekką wersję coroutines bez stosu, opartą na artykule Simona Tathama.
  • tonbit::coroutine – C++11 single .h asymetryczna implementacja coroutine poprzez ucontext / fiber
  • Coroutines wylądował w Clang w maju 2017, z libc++ implementacja w toku.
  • elle by Docker
  • oatpp-coroutines – stackless coroutines z schedulingiem zaprojektowanym dla operacji I/O na poziomie wysokiej współbieżności. Użyte w eksperymencie 5 milionów połączeń WebSocket przeprowadzonym przez Oat++. Część frameworka webowego Oat++.

Implementacje dla C#Edit

  • MindTouch Dream – Framework MindTouch Dream REST zapewnia implementację coroutines opartą na wzorcu iteratora C# 2.0
  • Caliburn – framework wzorców ekranowych Caliburn dla WPF wykorzystuje iteratory C# 2.0, aby ułatwić programowanie UI, szczególnie w scenariuszach asynchronicznych.
  • Power Threading Library – Power Threading Library autorstwa Jeffreya Richtera implementuje AsyncEnumerator, który zapewnia uproszczony model programowania asynchronicznego przy użyciu iteratorów opartych na coroutines.
  • Silnik gier Unity implementuje coroutines.
  • Servelat Pieces – Projekt Servelat Pieces autorstwa Yevhena Bobrova zapewnia przejrzystą asynchronię dla usług WCF Silverlight i możliwość asynchronicznego wywoływania dowolnych metod synchronicznych. Implementacja jest oparta na iteratorze Coroutines Caliburna i blokach iteratorów C#.
  • – Framework .NET 2.0+ zapewnia teraz funkcjonalność półkorutyny (generatora) poprzez wzorzec iteratora i słowo kluczowe yield.

C# 5.0 zawiera wsparcie dla składni await.

Implementacje dla ClojureEdit

Cloroutine jest biblioteką innej firmy zapewniającą wsparcie dla coroutines bez stosów w Clojure. Jest zaimplementowana jako makro, statycznie rozdzielające dowolny blok kodu na dowolne wywołania var i emitujące coroutine jako funkcję stateful.

Implementacje dla DEdit

D implementuje coroutines jako swoją standardową klasę biblioteczną Fiber Generator sprawia, że trywialnie jest eksponować funkcję fiber jako zakres wejściowy, dzięki czemu każdy fiber jest kompatybilny z istniejącymi algorytmami zakresu.

Implementacje dla JavaEdit

Istnieje kilka implementacji coroutines w Javie. Pomimo ograniczeń narzuconych przez abstrakcje Javy, JVM nie wyklucza tej możliwości. Istnieją cztery ogólnie stosowane metody, ale dwie z nich łamią przenośność kodu bajtowego wśród maszyn JVM zgodnych ze standardami.

  • Modyfikowane maszyny JVM. Możliwe jest zbudowanie poprawionej JVM, aby wspierać coroutines bardziej natywnie. Da Vinci JVM ma stworzone łaty.
  • Zmodyfikowany kod bajtowy. Funkcjonalność coroutine jest możliwa przez przepisanie zwykłego kodu bajtowego Javy, albo w locie albo w czasie kompilacji. Zestawy narzędzi obejmują Javaflow, Java Coroutines i Coroutines.
  • Specyficzne dla platformy mechanizmy JNI. Wykorzystują one metody JNI zaimplementowane w systemie operacyjnym lub bibliotekach C w celu dostarczenia funkcjonalności do maszyny JVM.
  • Abstrakcje wątku. Biblioteki coroutine, które są zaimplementowane przy użyciu wątków mogą być ciężkie, choć wydajność będzie się różnić w oparciu o implementację wątków w JVM.

Implementacje w JavaScriptEdit

  • node-fibers
    • Fibjs – fibjs jest runtime JavaScript zbudowanym na silniku Chrome’s V8 JavaScript. fibjs używa fibers-switch, stylu sync i modelu non-blocking I/O do budowania skalowalnych systemów.
  • Od ECMAScript 2015, dostarczana jest funkcjonalność coroutine bez stosu poprzez „generatory” i wyrażenia yield.

Implementacje dla KotlinEdit

Kotlin implementuje coroutines jako część biblioteki pierwszej strony.

Implementacje dla Modula-2Edit

Modula-2 zdefiniowana przez Wirtha implementuje coroutines jako część standardowej biblioteki SYSTEM.

Procedura NEWPROCESS() wypełnia kontekst podając jako parametry blok kodu i miejsce na stos, a procedura TRANSFER() przekazuje sterowanie do coroutine podając jako parametr kontekst coroutine.

Implementacja w MonoEdit

Mono Common Language Runtime posiada wsparcie dla kontinuów, z których można budować coroutiny.

Implementacja w .NET Framework jako fibersEdit

Podczas opracowywania .NET Framework 2.0 Microsoft rozszerzył projekt interfejsów API hostingu Common Language Runtime (CLR) w celu obsługi harmonogramowania opartego na fibersach z myślą o jego wykorzystaniu w trybie fiber dla serwera SQL. Przed wydaniem, wsparcie dla haka przełączania zadań ICLRTask::SwitchOut zostało usunięte z powodu ograniczeń czasowych.W rezultacie, użycie fiber API do przełączania zadań nie jest obecnie realną opcją w .NET Framework.

Implementacje dla PerlEdit

  • Coro

Korutiny są natywnie zaimplementowane we wszystkich backendach Raku.

Implementacje dla PHPEdit

  • Amphp
  • Coroutine zaimplementowane w sposób przypominający funkcje Pythona, oraz niektóre Go, wiele przykładów pokazujących tam kod przekonwertowany z taką samą liczbą linii i zachowaniem.

Implementacje dla PythonEdit

  • Python 2.5 implementuje lepsze wsparcie dla funkcjonalności coroutine-like, bazując na rozszerzonych generatorach (PEP 342)
  • Python 3.3 poprawia te możliwości, wspierając delegowanie do podgeneratora (PEP 380)
  • Python 3.4 wprowadza wszechstronny framework asynchronicznego I/O, ustandaryzowany w PEP 3156, który obejmuje coroutiny wykorzystujące delegowanie do podgeneratora
  • Python 3.5 wprowadza jawną obsługę coroutinów ze składnią async/await (PEP 0492).
  • Od Pythona 3.7 async/await stały się zarezerwowanymi słowami kluczowymi.
  • Eventlet
  • Greenlet
  • gevent
  • stackless python
  • Abandoned
    • Shrapnel (ostatnie wydanie 2015)
    • Kamaelia (ostatnie wydanie. 2010)
    • cogen (ostatnie wydanie 2009)
    • multitask (ostatnie wydanie 2007)
    • chiral

Implementacje dla RubyEdit

  • Ruby 1.9 obsługuje coroutines natywnie, które są zaimplementowane jako fibers, które są pół-koroutines.
  • An implementation by Marc De Scheemaecker
  • Ruby 2.5 i wyższe obsługuje coroutines natywnie, które są zaimplementowane jako fibers
  • An implementation by Thomas W Branson

Implementacje dla RustEdit

Rust obsługuje coroutines od wersji 1.39 .Istnieje również alternatywny asynchroniczny runtime (starszy projekt niż standardowy runtime rdzy) : tokio

Implementacje dla ScalaEdit

Scala Coroutines jest implementacją coroutine dla Scali. Implementacja ta jest rozszerzeniem na poziomie biblioteki, które opiera się na systemie makr Scali do statycznego przekształcania fragmentów programu w obiekty korutynowe. Jako taka, implementacja ta nie wymaga modyfikacji w JVM, więc jest w pełni przenośna pomiędzy różnymi JVM i działa z alternatywnymi backendami Scali, takimi jak Scala.js, która kompiluje się do JavaScript.

Scala Coroutines polegają na makrze coroutine, które przekształca normalny blok kodu w definicję coroutine. Taka definicja korutyny może być wywołana za pomocą operacji call, która tworzy ramkę korutyny. Ramka coroutine może zostać wznowiona za pomocą metody resume, która wznawia wykonywanie ciała coroutine, aż do osiągnięcia słowa kluczowego yieldval, które zawiesza ramkę coroutine. Korutyny Scali eksponują również metodę snapshot, która efektywnie duplikuje korutynę. Szczegółowe opisy coroutines Scala z migawkami pojawiły się na ECOOP 2018, wraz z ich formalnym modelem.

Implementacje dla SchemeEdit

Ponieważ Scheme zapewnia pełne wsparcie dla kontynuacji, implementacja coroutines jest niemal trywialna, wymagając jedynie, aby kolejka kontynuacji była utrzymywana.

Implementacje dla SmalltalkEdit

Ponieważ w większości środowisk Smalltalk stos wykonawczy jest obywatelem pierwszej klasy, coroutines mogą być zaimplementowane bez dodatkowej biblioteki lub wsparcia maszyny wirtualnej.

Implementacje dla SwiftEdit

  • SwiftCoroutine – biblioteka coroutines Swift dla iOS, macOS i Linux.

Implementacje dla Tool Command Language (Tcl)Edit

Od wersji 8.6, Tool Command Language obsługuje coroutines w rdzeniu języka.

Implementacje dla ValaEdit

Vala implementuje natywne wsparcie dla coroutines. Zostały one zaprojektowane do użycia z pętlą główną Gtk, ale mogą być używane samodzielnie, jeśli zapewni się, że wywołanie zwrotne końca nigdy nie będzie musiało być wywołane przed wykonaniem przynajmniej jednej operacji yield.

Implementacje w językach asemblerowychEdit

Zależne od maszyny języki asemblerowe często dostarczają bezpośrednich metod wykonywania coroutine. Na przykład, w MACRO-11, języku asemblera rodziny minikomputerów PDP-11, „klasyczne” przełączanie korutyny jest wykonywane przez instrukcję „JSR PC,@(SP)+”, która skacze pod adres wyciągnięty ze stosu i wypycha bieżący (tj. adres następnej) instrukcji na stos. Na VAXen (w Macro-32) porównywalną instrukcją jest „JSB @(SP)+”. Nawet na Motoroli 6809 jest instrukcja „JSR „; zwróć uwagę na „++”, ponieważ 2 bajty (adresu) są usuwane ze stosu. Ta instrukcja jest często używana w (standardowym) „monitorze” Assist 09.

.

Dodaj komentarz