Coroutine

2003 óta a legnépszerűbb programozási nyelvek közül sok, köztük a C és származékai nem támogatják közvetlenül a coroutine-okat a nyelvben vagy a szabványos könyvtárakban. Ez nagyrészt a veremalapú szubrutinok megvalósításának korlátai miatt van így. Kivételt képez a Boost.Context nevű C++ könyvtár, amely a boost könyvtárak része, és amely támogatja a kontextuscserét ARM, MIPS, PowerPC, SPARC és x86 rendszereken a POSIX, Mac OS X és Windows rendszereken. A Boost.Context-re Coroutine-ok építhetők.

Ahol egy coroutine lenne a mechanizmus természetes megvalósítása, de nem áll rendelkezésre, ott a tipikus válasz egy closure – egy szubrutin állapotváltozókkal (statikus változók, gyakran boolean flag-ek) ellátott szubrutin – használata, amely a hívások között fenntartja a belső állapotot, és átadja a vezérlést a megfelelő pontra. A kódon belüli feltételes zárlatok az állapotváltozók értékei alapján különböző kódútvonalak végrehajtását eredményezik az egymást követő hívásoknál. Egy másik tipikus válasz az explicit állapotgép megvalósítása egy nagy és összetett switch utasítás formájában vagy egy goto utasításon keresztül, különösen egy számított goto utasításon keresztül. Az ilyen implementációkat nehezen érthetőnek és karbantarthatónak tartják, és ez a koroutin-támogatás egyik motivációja.

A szálak és kisebb mértékben a szálak jelentik ma a koroutinok alternatíváját a mainstream programozási környezetekben. A szálak az egyidejűleg futó kódrészek valós idejű együttműködő interakciójának kezelésére nyújtanak lehetőséget. A szálak széles körben elérhetőek a C-t támogató környezetekben (és számos más modern nyelvben natívan támogatottak), sok programozó számára ismerősek, és általában jól implementáltak, jól dokumentáltak és jól támogatottak. Mivel azonban egy nagy és nehéz problémát oldanak meg, sok nagy teljesítményű és összetett eszközt tartalmaznak, és ennek megfelelően nehéz a tanulási folyamatuk. Így amikor csak egy coroutine-ra van szükség, a szál használata túlzás lehet.

A szálak és a coroutine-ok közötti egyik fontos különbség az, hogy a szálak jellemzően preemptív ütemezésűek, míg a coroutine-ok nem. Mivel a szálak bármelyik pillanatban átütemezhetők és egyidejűleg hajthatók végre, a szálakat használó programoknak óvatosnak kell lenniük a zárolással. Ezzel szemben, mivel a coroutine-ok csak a program meghatározott pontjain ütemezhetők át, és nem futnak párhuzamosan, a coroutine-okat használó programok gyakran teljesen elkerülhetik a zárolást. Ezt a tulajdonságot az eseményvezérelt vagy aszinkron programozás egyik előnyeként is említik.

Mivel a szálak kooperatív ütemezésűek, ideális alapot nyújtanak a fenti coroutine-ok megvalósításához. A szálak rendszerbeli támogatása azonban gyakran hiányzik a szálakéhoz képest.

Implementációk CEdit

Az általános célú coroutine-ok megvalósításához egy második hívási veremet kell beszerezni, amit a C nyelv közvetlenül nem támogat. Ennek megbízható (bár platform-specifikus) módja, hogy egy kis inline assembly segítségével explicit módon manipuláljuk a veremmutatót a coroutine kezdeti létrehozása során. Ezt a megközelítést ajánlja Tom Duff a Protothreads által használt módszerrel szembeni relatív előnyeiről szóló vitában. Azokon a platformokon, amelyek biztosítják a POSIX sigaltstack rendszerhívást, egy második hívási verem elérhető egy rugós függvény meghívásával egy jelkezelőn belülről, hogy ugyanazt a célt elérjük hordozható C-ben, némi extra komplexitás árán. A POSIX vagy a Single Unix Specification (SUSv3) szabványnak megfelelő C könyvtárak olyan rutinokat biztosítottak, mint a getcontext, setcontext, makecontext és swapcontext, de ezeket a függvényeket a POSIX 1.2008-ban elavultnak nyilvánították.

Amikor a második hívási veremet a fent felsorolt módszerek valamelyikével megszereztük, a standard C könyvtár setjmp és longjmp függvényeit használhatjuk a koroutinok közötti váltások megvalósítására. Ezek a függvények elmentik, illetve visszaállítják a veremmutatót, a programszámlálót, a hívásmentett regisztereket és bármely más, az ABI által megkövetelt belső állapotot, úgy, hogy a coroutine-ba való visszatérés a megadás után visszaállítja az összes olyan állapotot, amely egy függvényhívásból való visszatéréskor visszaállna. Minimalista implementációk, amelyek nem használják a setjmp és longjmp függvényeket, ugyanezt az eredményt elérhetik egy kis inline assembly blokk segítségével, amely csupán a veremmutatót és a programszámlálót cseréli ki, az összes többi regisztert pedig elnyomja. Ez jelentősen gyorsabb lehet, mivel a setjmp-nek és a longjmp-nek konzervatív módon kell tárolnia minden olyan regisztert, amely az ABI szerint használatban lehet, míg a clobber módszer lehetővé teszi a fordító számára, hogy csak azt tárolja (a veremre kiöntve), amiről tudja, hogy valóban használatban van.

A közvetlen nyelvi támogatás hiánya miatt sok szerző írt saját könyvtárakat a coroutine-okhoz, amelyek elrejtik a fenti részleteket. Russ Cox libtask könyvtára jó példa erre a műfajra. Használja a kontextusfüggvényeket, ha azokat a natív C könyvtár biztosítja; egyébként saját implementációkat biztosít ARM, PowerPC, Sparc és x86 számára. Más figyelemre méltó implementációk közé tartozik a libpcl, coro, lthread, libCoroutine, libconcurrency, libcoro, ribs2, libdill., libaco és libco.

A fenti általános megközelítés mellett számos kísérlet történt arra, hogy a coroutine-okat C-ben alprogramok és makrók kombinációival közelítsék. Simon Tatham hozzájárulása, amely Duff eszközén alapul, figyelemre méltó példája a műfajnak, és a Protothreads és hasonló implementációk alapját képezi. Duff kifogásai mellett Tatham saját megjegyzéseiben őszintén értékeli a megközelítés korlátait: “Amennyire én tudom, ez a legrosszabb C-hekkelés, amit valaha is láttam komoly termelési kódban”. Ennek a közelítésnek a fő hiányosságai, hogy mivel nem tartanak fenn külön veremkeretet minden egyes coroutine számára, a helyi változók nem maradnak meg a függvényből való kilépések során, nem lehetséges többszörös belépések a függvénybe, és a vezérlés csak a legfelső szintű rutinból adható át.

Implementációk a C++Edit

  • A C++ coroutines TS (Technical Specification), a C++ nyelvi kiterjesztések szabványa a coroutine-szerű viselkedés egy verem nélküli részhalmazára, fejlesztés alatt áll. A Visual C++ és a Clang már támogatja jelentős részeit az std::experimental névtérben. coroutines Technical Specification
  • Boost.Coroutine – Oliver Kowalke által készített, az 1.53-as verzió óta a boost hivatalosan kiadott, hordozható coroutine könyvtára. A könyvtár a Boost.Contextre támaszkodik, és támogatja az ARM, MIPS, PowerPC, SPARC és X86 rendszereket POSIX, Mac OS X és Windows alatt.
  • Boost.Coroutine2 – szintén Oliver Kowalke által készített, a boost 1.59-es verziója óta egy modernizált hordozható coroutine könyvtár. Kihasználja a C++11 jellemzőit, de megszünteti a szimmetrikus coroutine-ok támogatását.
  • Mordor – 2010-ben a Mozy nyílt forráskódúvá tett egy coroutine-okat megvalósító C++ könyvtárat, amelynek hangsúlyt fektet arra, hogy az aszinkron I/O-t egy ismertebb szekvenciális modellbe absztrahálja.
  • CO2 – C++ preprocesszor trükkökön alapuló stackless coroutine, await/yield emulációt biztosít.
  • ScummVM – A ScummVM projekt Simon Tatham cikke alapján implementálja a stackless coroutine-ok egy könnyített változatát.
  • tonbit::coroutine – C++11 single .h aszimmetrikus coroutine implementáció az ucontext / fiber segítségével
  • A coroutine-ok 2017 májusában landoltak a Clangban, a libc++ implementáció folyamatban van.
  • elle by Docker
  • oatpp-coroutines – stackless coroutine-ok ütemezéssel, amelyeket magas párhuzamossági szintű I/O műveletekre terveztek. Az Oat++ által az 5 millió WebSocket kapcsolat kísérletben használták. Az Oat++ webes keretrendszer része.

Implementációk C#Edit

  • MindTouch Dream – A MindTouch Dream REST keretrendszer a C# 2.0 iterátor mintán alapuló coroutine-ok implementációját biztosítja
  • Caliburn – A Caliburn képernyőminta-keretrendszer WPF-hez C# 2.0 iterátorokat használ a felhasználói felület programozás megkönnyítésére, különösen aszinkron forgatókönyvekben.
  • Power Threading Library – A Jeffrey Richter által készített Power Threading Library egy AsyncEnumerator-t valósít meg, amely egyszerűsített aszinkron programozási modellt biztosít iterátor alapú coroutine-ok segítségével.
  • A Unity játékmotor coroutine-okat valósít meg.
  • Servelat Pieces – A Yevhen Bobrov által készített Servelat Pieces projekt átlátható aszinkronitást biztosít Silverlight WCF szolgáltatásokhoz és lehetőséget biztosít bármely szinkron metódus aszinkron hívására. Az implementáció a Caliburn Coroutines iterátorán és a C# iterátor blokkokon alapul.
  • – A .NET 2.0+ keretrendszer mostantól fél-koroutin (generátor) funkcionalitást biztosít az iterátor mintán és a yield kulcsszón keresztül.

A C# 5.0 tartalmazza az await szintaxis támogatását.

Implementációk ClojureEdit

A Cloroutine egy harmadik féltől származó könyvtár, amely támogatást nyújt a stackless coroutine-okhoz Clojure-ban. Makróként van implementálva, statikusan feloszt egy tetszőleges kódblokkot tetszőleges var hívásokon, és a coroutine-t állapotfüggvényként bocsátja ki.

Implementációk a DEdithez

A D a coroutine-okat a szabványos könyvtári osztályaként valósítja meg Fiber A generátor triviálissá teszi egy fiber függvény input tartományként való exponálását, így bármely fiber kompatibilissé válik a meglévő tartomány algoritmusokkal.

Implementációk a JavaEdithez

A coroutine-oknak több implementációja is létezik Java-ban. A Java absztrakciói által előírt korlátok ellenére a JVM nem zárja ki a lehetőséget. Négy általános módszert használnak, de kettő megtöri a bytecode hordozhatóságát a szabványoknak megfelelő JVM-ek között.

  • Módosított JVM-ek. Lehetőség van egy javított JVM építésére, hogy natívabban támogassa a coroutine-okat. A Da Vinci JVM-hez már készültek patchek.
  • Módosított bytecode. A coroutine funkcionalitás a normál Java bájtkód átírásával lehetséges, akár menet közben, akár fordítási időben. Az eszközkészletek közé tartozik a Javaflow, a Java Coroutines és a Coroutines.
  • Platformspecifikus JNI mechanizmusok. Ezek az operációs rendszerben vagy a C könyvtárakban implementált JNI módszereket használnak a JVM számára a funkcionalitás biztosításához.
  • Szálak absztrakciói. A szálak segítségével megvalósított Coroutine-könyvtárak nehézkesek lehetnek, bár a teljesítmény a JVM szálimplementációjától függően változik.

Implementációk JavaScriptEdit

  • node-fibers
    • Fibjs – A fibjs egy JavaScript-futtató, amely a Chrome V8 JavaScript-motorjára épül. A fibjs a fibers-kapcsolást, a sync-stílust és a nem blokkoló I/O modellt használja a skálázható rendszerek építéséhez.
  • Az ECMAScript 2015 óta stackless coroutine funkcionalitást biztosít “generátorokon” és yield-kifejezéseken keresztül.

Implementációk KotlinEdit

A Kotlin egy elsőrendű könyvtár részeként implementálja a coroutine-okat.

Implementációk Modula-2Edit

A Wirth által definiált Modula-2 a standard SYSTEM könyvtár részeként implementálja a coroutine-okat.

A NEWPROCESS() eljárás kitölti a kontextust, ha paraméterként egy kódblokkot és egy veremhelyet kapunk, a TRANSFER() eljárás pedig átadja a vezérlést egy coroutine-nak, ha paraméterként a coroutine kontextusát kapjuk.

Megvalósítás a MonoEdit

A Mono Common Language Runtime támogatja a folytatásokat, amelyekből coroutine-ok építhetők.

Megvalósítás a .NET Frameworkben szálankéntEdit

A .NET Framework 2.0 fejlesztése során a Microsoft kiterjesztette a Common Language Runtime (CLR) hosting API-k tervezését a szál alapú ütemezés kezelésére, szem előtt tartva az SQL szerver szálankénti használatát. A kiadás előtt az ICLRTask::SwitchOut feladatváltó horog támogatását időbeli korlátok miatt megszüntették, következésképpen a .NET Frameworkben jelenleg nem lehetséges a szálas API használata a feladatok váltására.

Implementációk PerlEdit

  • Coro

A Raku összes backendjében natívan implementálva vannak a koroutinok.

Implementációk PHPEdit

  • Amphp
  • Coroutine implementálva Python függvényekre hasonlító módon, és néhány Go, sok példa mutatja ott a kódot átalakítva ugyanolyan sorszámmal és viselkedéssel.

Implementációk PythonEdit

  • Python 2.5 jobb támogatást valósít meg a coroutine-szerű funkcionalitáshoz, kiterjesztett generátorok alapján (PEP 342)
  • Python 3.3 javítja ezt a képességet azáltal, hogy támogatja az algenerátorba való delegálást (PEP 380)
  • A Python 3.4 bevezet egy átfogó aszinkron I/O keretrendszert, amelyet a PEP 3156 szabványosít, amely magában foglalja az algenerátorba való delegálást kihasználó coroutine-okat
  • A Python 3.5 bevezeti az async/await szintaxisú coroutine-ok explicit támogatását (PEP 0492).
  • A Python 3.7 óta az async/await foglalt kulcsszavak lettek .
  • Eventlet
  • Greenlet
  • gevent
  • stackless python
  • Abandoned
    • Shrapnel (utolsó kiadás 2015)
    • Kamaelia (utolsó kiadás 2015)
    • Kamaelia (utolsó kiad. 2010)
    • cogen (utolsó kiadás 2009)
    • multitask (utolsó kiadás 2007)
    • chiral

Implementációk RubyEdit

  • Ruby 1.9 natívan támogatja a coroutine-okat, amelyek szálakként vannak implementálva, amelyek fél-coroutine-ok.
  • A megvalósítás Marc De Scheemaecker
  • A Ruby 2.5 és újabb verziók natívan támogatják a coroutine-okat, amelyek szálakként vannak megvalósítva
  • A megvalósítás Thomas W Branson

Implementációk a RustEdit

Rusthoz A Rust az 1. verzió óta támogatja a coroutine-okat.39 .Van egy alternatív aszinkron futási idő is (régebbi projekt, mint a rust standard futási ideje) : tokio

Implementations for ScalaEdit

Scala Coroutines egy coroutine implementáció Scala számára. Ez az implementáció egy könyvtár szintű kiterjesztés, amely a Scala makrorendszerére támaszkodik, hogy a programrészeket statikusan coroutine objektumokká alakítsa. Mint ilyen, ez az implementáció nem igényel módosításokat a JVM-ben, így teljesen hordozható a különböző JVM-ek között, és működik alternatív Scala backendekkel, mint például a Scala.js, amely JavaScriptre fordít.

A Scala Coroutines a coroutine makróra támaszkodik, amely egy normál kódblokkot coroutine definícióvá alakít. Egy ilyen coroutine definíciót a call művelettel lehet meghívni, amely egy coroutine keretet példányosít. Egy coroutine keretet a resume művelettel lehet folytatni, amely folytatja a coroutine testének végrehajtását, egészen a yieldval kulcsszó eléréséig, amely felfüggeszti a coroutine keretet. A Scala Coroutine-ok egy snapshot metódust is kitesznek, amely gyakorlatilag duplikálja a coroutine-t. A Scala coroutine-ok részletes leírása pillanatképekkel az ECOOP 2018-on jelent meg, a formális modelljükkel együtt.

Implementációk SchemeEdit

Mivel a Scheme teljes támogatást nyújt a folytatásokhoz, a koroutinok megvalósítása szinte triviális, csak egy folytatási sor fenntartása szükséges.

Implementációk SmalltalkEdit

Mivel a legtöbb Smalltalk környezetben a végrehajtási verem első osztályú polgár, a koroutinok további könyvtár vagy VM támogatás nélkül is megvalósíthatók.

Implementációk SwiftEdit

  • SwiftCoroutine – Swift coroutine könyvtár iOS, macOS és Linux számára.

Implementációk Tool Command Language (Tcl)Edit

A 8. verzió óta.6 óta a Tool Command Language támogatja a coroutine-okat az alapnyelvben.

Implementációk ValaEdit

Vala natív támogatást valósít meg a coroutine-okhoz. Ezeket úgy tervezték, hogy egy Gtk Main Looppal együtt használhatók legyenek, de önmagukban is használhatók, ha ügyelnek arra, hogy a befejező visszahívást soha ne kelljen meghívni, mielőtt legalább egy yield-t végrehajtanánk.

Implementációk assembly nyelvekenEdit

A gépfüggő assembly nyelvek gyakran biztosítanak közvetlen módszereket a coroutine végrehajtásához. Például a MACRO-11-ben, a PDP-11 miniszámítógép-család assembly nyelvében a “klasszikus” coroutine váltás a “JSR PC,@(SP)+” utasítással történik, amely a veremről kiugrott címre ugrik, és az aktuális (azaz a következő) utasítás címét a veremre tolja. VAXen (Macro-32-ben) a hasonló utasítás a “JSB @(SP)+”. Még a Motorola 6809-en is létezik a “JSR ” utasítás; figyeljük meg a “++”-t, mivel 2 bájt (cím) kerül ki a veremről. Ezt az utasítást sokat használják a (szabványos) “monitor” Assist 09.

-ban.

Szólj hozzá!