Coroutine

Vid 2003 har många av de mest populära programmeringsspråken, inklusive C och dess derivat, inget direkt stöd för coroutines i språket eller deras standardbibliotek. Detta beror till stor del på begränsningarna för stackbaserad implementering av subroutiner. Ett undantag är C++-biblioteket Boost.Context, en del av boost-biblioteken, som stöder context swapping på ARM, MIPS, PowerPC, SPARC och x86 på POSIX, Mac OS X och Windows. Coroutiner kan byggas på Boost.Context.

I situationer där en coroutin skulle vara det naturliga genomförandet av en mekanism, men inte är tillgänglig, är den typiska lösningen att använda en closure – en underrutin med tillståndsvariabler (statiska variabler, ofta boolska flaggor) för att bibehålla ett internt tillstånd mellan anropen, och för att överföra kontrollen till rätt punkt. Villkor i koden leder till att olika kodvägar utförs vid på varandra följande anrop, baserat på tillståndsvariablernas värden. Ett annat typiskt svar är att implementera en explicit tillståndsmaskin i form av en stor och komplex switch-angivelse eller via en goto-angivelse, särskilt en beräknad goto-angivelse. Sådana implementationer anses vara svåra att förstå och underhålla och är ett motiv för stöd för coroutiner.

Threads, och i mindre utsträckning fibers, är ett alternativ till coroutiner i vanliga programmeringsmiljöer idag. Trådar ger möjligheter att hantera den kooperativa interaktionen i realtid mellan samtidigt exekverande kodstycken. Trådar är allmänt tillgängliga i miljöer som stöder C (och stöds naturligt i många andra moderna språk), är bekanta för många programmerare och är vanligtvis välimplementerade, väldokumenterade och välstödda. Eftersom de löser ett stort och svårt problem innehåller de dock många kraftfulla och komplexa funktioner och har en motsvarande svår inlärningskurva. När en coroutine är allt som behövs kan det därför vara överflödigt att använda en tråd.

En viktig skillnad mellan trådar och coroutines är att trådar vanligtvis är preemptivt schemalagda medan coroutines inte är det. Eftersom trådar kan schemaläggas om när som helst och kan köras samtidigt måste program som använder trådar vara försiktiga med låsning. Eftersom koroutiner däremot endast kan omplaneras vid specifika tidpunkter i programmet och inte utförs samtidigt, kan program som använder koroutiner ofta undvika låsning helt och hållet. Denna egenskap nämns också som en fördel med händelsestyrd eller asynkron programmering.

Då fibrer schemaläggs i samarbete ger de en idealisk bas för att implementera coroutines ovan. Systemstödet för fibers är dock ofta bristfälligt jämfört med det för trådar.

Implementationer för CEdit

För att implementera allmängiltiga coroutiner måste en andra anropsstack erhållas, vilket är en funktion som inte stöds direkt av C-språket. Ett tillförlitligt (om än plattformsspecifikt) sätt att åstadkomma detta är att använda en liten mängd inline-assemblering för att explicit manipulera stackpekaren under den första skapelsen av coroutinen. Detta är den metod som rekommenderas av Tom Duff i en diskussion om dess relativa fördelar jämfört med den metod som används av Protothreads. På plattformar som tillhandahåller systemanropet POSIX sigaltstack kan en andra anropstack erhållas genom att anropa en språngbrädesfunktion från en signalhanterare för att uppnå samma mål i portabel C, till priset av en viss extra komplexitet. C-bibliotek som följer POSIX eller Single Unix Specification (SUSv3) tillhandahöll sådana rutiner som getcontext, setcontext, makecontext och swapcontext, men dessa funktioner förklarades föråldrade i POSIX 1.2008.

När en andra anropsstack har erhållits med någon av de metoder som anges ovan, kan funktionerna setjmp och longjmp i C:s standardbibliotek sedan användas för att genomföra växlingarna mellan coroutiner. Dessa funktioner sparar respektive återställer stackpekaren, programräknaren, de register som sparats i callee och alla andra interna tillstånd som krävs av ABI, så att om man återvänder till en coroutin efter att ha lämnat återställs allt det tillstånd som skulle återställas när man återvänder från ett funktionsanrop. Minimalistiska tillämpningar, som inte använder sig av funktionerna setjmp och longjmp, kan uppnå samma resultat genom ett litet block av inline-assemblering som endast byter ut stackpointern och programräknaren, och som sparar alla andra register. Detta kan vara betydligt snabbare, eftersom setjmp och longjmp konservativt måste lagra alla register som kan vara i bruk enligt ABI, medan clobber-metoden gör det möjligt för kompilatorn att lagra (genom att spilla till stacken) endast det som den vet att det faktiskt är i bruk.

På grund av bristen på direkt språkstöd har många författare skrivit sina egna bibliotek för coroutiner som döljer ovanstående detaljer. Russ Cox’ bibliotek libtask är ett bra exempel på denna genre. Det använder kontextfunktionerna om de tillhandahålls av det inhemska C-biblioteket, annars tillhandahåller det egna implementationer för ARM, PowerPC, Sparc och x86. Andra anmärkningsvärda implementeringar är libpcl, coro, lthread, libCoroutine, libconcurrency, libcoro, ribs2, libdill., libaco och libco.

Inom det allmänna tillvägagångssättet ovan har flera försök gjorts för att tillnärma coroutiner i C med kombinationer av underrutiner och makron. Simon Tathams bidrag, baserat på Duff’s device, är ett anmärkningsvärt exempel på genren och ligger till grund för Protothreads och liknande implementeringar. Förutom Duffs invändningar ger Tathams egna kommentarer en öppenhjärtig utvärdering av begränsningarna i detta tillvägagångssätt: ”Såvitt jag vet är detta det värsta C-hackaffären som någonsin setts i seriös produktionskod”. De största bristerna med denna approximation är att lokala variabler inte bevaras över olika nivåer av funktionen, eftersom man inte upprätthåller en separat stapelram för varje coroutine, att det inte är möjligt att ha flera ingångar till funktionen och att kontrollen endast kan överlåtas från den översta rutinen.

Genomföranden för C++Edit

  • C++ coroutines TS (Technical Specification), en standard för C++-språkutvidgningar för en stacklös delmängd av coroutine-liknande beteende, håller på att utvecklas. Visual C++ och Clang stöder redan stora delar i namnområdet std::experimental. coroutines Technical Specification
  • Boost.Coroutine – skapad av Oliver Kowalke, är det officiella släppta portabla coroutinebiblioteket för boost sedan version 1.53. Biblioteket bygger på Boost.Context och har stöd för ARM, MIPS, PowerPC, SPARC och X86 på POSIX, Mac OS X och Windows.
  • Boost.Coroutine2 – också skapad av Oliver Kowalke, är ett moderniserat portabelt coroutinebibliotek sedan boost version 1.59. Det drar nytta av C++11-funktioner, men tar bort stödet för symmetriska coroutiner.
  • Mordor – 2010 öppnade Mozy ett C++-bibliotek som implementerar coroutiner, med tonvikt på att använda dem för att abstrahera asynkron I/O till en mer välbekant sekventiell modell.
  • CO2 – stackless coroutine baserad på C++ preprocessortrick, som ger await/yield-emulering.
  • ScummVM – ScummVM-projektet implementerar en lättviktsversion av stackless coroutines baserad på Simon Tathams artikel.
  • tonbit::coroutine – C++11 single .h asymmetrisk coroutineimplementering via ucontext / fiber
  • Coroutines landade i Clang i maj 2017, med libc++implementering på gång.
  • elle by Docker
  • oatpp-coroutines – stackless coroutines med schemaläggning utformad för I/O-operationer med hög valutanivå. Används i experimentet med 5 miljoner WebSocket-anslutningar av Oat++. En del av Oat++ webbramverk.

Implementationer för C#Edit

  • MindTouch Dream – MindTouch Dream REST-ramverket tillhandahåller en implementering av coroutiner baserade på C# 2.0-iteratormönstret
  • Caliburn – Ramverket för skärmmönster för Caliburn för WPF använder C# 2.0-iteratorer för att underlätta UI-programmering, särskilt i asynkrona scenarier.
  • Power Threading Library – Power Threading Library av Jeffrey Richter implementerar en AsyncEnumerator som ger en förenklad asynkron programmeringsmodell med hjälp av iteratorbaserade coroutines.
  • Spelmotorn Unity implementerar coroutines.
  • Servelat Pieces – Servelat Pieces-projektet av Yevhen Bobrov ger transparent asynkronitet för WCF-tjänster i Silverlight och möjlighet att asynkront anropa alla synkrona metoder. Implementationen är baserad på Caliburns Coroutines-iterator och C#-iteratorblock.
  • – .NET 2.0+ Framework tillhandahåller nu semikoroutinfunktionalitet (generator) genom iteratormönstret och nyckelordet yield.

C## 5.0 innehåller stöd för await-syntaxen.

Implementationer för ClojureEdit

Cloroutine är ett bibliotek från en tredje part som ger stöd för stackless coroutines i Clojure. Det implementeras som ett makro som statiskt delar upp ett godtyckligt kodblock på godtyckliga var-anrop och skickar ut coroutinen som en stateful funktion.

Implementationer för DEdit

D implementerar coroutiner som sin standardbiblioteksklass Fiber En generator gör det trivialt att exponera en fiberfunktion som ett ingångsintervall, vilket gör att alla fiber är kompatibla med befintliga intervallalgoritmer.

Implementationer för JavaEdit

Det finns flera implementationer för coroutiner i Java. Trots de begränsningar som Javas abstraktioner medför utesluter inte JVM möjligheten. Det finns fyra allmänna metoder som används, men två av dem bryter mot bytecodeportabiliteten bland standardkompatibla JVM:er.

  • Modifierade JVM:er. Det är möjligt att bygga en patched JVM för att stödja coroutines mer nativt. Da Vinci JVM har fått patchar skapade.
  • Modifierad bytekod. Coroutine-funktionalitet är möjlig genom att skriva om vanlig Java-bytecode, antingen i farten eller vid kompilering. Verktygslådor inkluderar Javaflow, Java Coroutines och Coroutines.
  • Platformspecifika JNI-mekanismer. Dessa använder JNI-metoder som implementerats i operativsystemet eller C-biblioteken för att tillhandahålla funktionaliteten till JVM:n.
  • Thread abstractions. Coroutine-bibliotek som implementeras med hjälp av trådar kan vara tunga, även om prestandan varierar beroende på JVM:s trådimplementering.

Implementeringar i JavaScriptEdit

  • node-fibers
    • Fibjs – fibjs är en JavaScript-körningstid som bygger på Chromes V8 JavaScript-motor. fibjs använder fibers-switch, sync-stil och icke-blockerande I/O-modell för att bygga skalbara system.
  • Sedan ECMAScript 2015 tillhandahålls stackless coroutine-funktionalitet genom ”generatorer” och yield-uttryck.

Implementationer för KotlinEdit

Kotlin implementerar coroutines som en del av ett förstapartsbibliotek.

Genomföranden för Modula-2Edit

Modula-2 enligt Wirths definition implementerar koroutiner som en del av standardbiblioteket SYSTEM.

Proceduren NEWPROCESS() fyller en kontext med ett kodblock och utrymme för en stack som parametrar, och proceduren TRANSFER() överför kontrollen till en koroutin med koroutinens kontext som parameter.

Genomförande i MonoEdit

Mono Common Language Runtime har stöd för fortsättningar, från vilka coroutiner kan byggas.

Genomförande i .NET Framework som fibersEdit

Under utvecklingen av .NET Framework 2.0 utökade Microsoft utformningen av värd-API:erna för Common Language Runtime (CLR) så att de kan hantera fiberbaserad schemaläggning med sikte på att den ska användas i fiberläge för SQL-server. Innan lanseringen togs stödet för uppgiftsväxlingskroken ICLRTask::SwitchOut bort på grund av tidsbrist. följaktligen är användningen av fiber-API:et för att växla uppgifter för närvarande inte ett genomförbart alternativ i .NET Framework.

Implementationer för PerlEdit

  • Coro

Coroutiner implementeras nativt i alla Raku-baksidor.

Implementationer för PHPEdit

  • Amphp
  • Coroutine implementeras på ett sätt som liknar Python-funktioner, och en del Go, många exempel som visar att koden konverteras med samma antal rader och beteende.

Implementationer för PythonEdit

  • Python 2.5 implementerar bättre stöd för koroutineliknande funktionalitet, baserat på utökade generatorer (PEP 342)
  • Python 3.3 förbättrar denna förmåga genom att stödja delegering till en undergenerator (PEP 380)
  • Python 3.4 introducerar ett omfattande ramverk för asynkrona I/O som standardiseras i PEP 3156, vilket inkluderar coroutiner som utnyttjar delegering till en undergenerator
  • Python 3.5 introducerar uttryckligt stöd för coroutiner med async/await-syntaxen (PEP 0492).
  • Därför har async/await blivit reserverade nyckelord sedan Python 3.7 .
  • Eventlet
  • Greenlet
  • gevent
  • stackless python
  • Abandoned
    • Shrapnel (sista utgåvan 2015)
    • Kamaelia (sista utgåvan 2010)
    • cogen (senaste versionen 2009)
    • multitask (senaste versionen 2007)
    • chiral

Genomföranden för RubyEdit

  • Ruby 1.9 har nativt stöd för coroutines som implementeras som fibers, som är semi-coroutines.
  • En implementering av Marc De Scheemaecker
  • Ruby 2.5 och högre stöder coroutines nativt som implementeras som fibers
  • En implementering av Thomas W Branson

Implementeringar för RustEdit

Rust stöder coroutines sedan version 1.39 .Det finns också en alternativ asynkron körtid (äldre projekt än rusts standardkörtid) : tokio

Implementationer för ScalaEdit

Scala Coroutines är en implementering av coroutiner för Scala. Denna implementering är ett tillägg på biblioteksnivå som förlitar sig på Scalas makrosystem för att statiskt omvandla delar av programmet till coroutineobjekt. Som sådan kräver denna implementering inga ändringar i JVM:en, så den är helt portabel mellan olika JVM:er och fungerar med alternativa Scala-bakgrunder, till exempel Scala.js, som kompilerar till JavaScript.

Scala Coroutines förlitar sig på coroutine-makrot som omvandlar ett normalt kodblock till en coroutine-definition. En sådan coroutinedefinition kan åberopas med call-operationen, som instansierar en coroutinram. En koroutinram kan återupptas med resume-metoden, som återupptar utförandet av koroutinens kropp, tills man når ett yieldval-nyckelord, som avbryter koroutinramen. Scala-coroutiner har också en snapshot-metod, som effektivt duplicerar coroutinen. En detaljerad beskrivning av Scala-coroutiner med ögonblicksbilder dök upp på ECOOP 2018, tillsammans med deras formella modell.

Implementationer för SchemeEdit

Med tanke på att Scheme har fullt stöd för fortsättningar är det nästan trivialt att implementera koroutiner, eftersom det bara krävs att en kö av fortsättningar upprätthålls.

Implementationer för SmalltalkEdit

Med tanke på att exekveringsstacken i de flesta Smalltalk-miljöer är en första klassens medborgare, kan koroutiner implementeras utan ytterligare stöd för bibliotek eller VM.

Implementationer för SwiftEdit

  • SwiftCoroutine – Swift-bibliotek för koroutiner för iOS, macOS och Linux.

Implementation för Tool Command Language (Tcl)Edit

Sedan version 8.6 har Tool Command Language stöd för koroutiner i kärnspråket.

Implementationer för ValaEdit

Vala implementerar inhemskt stöd för koroutiner. De är utformade för att användas tillsammans med en Gtk Main Loop, men kan användas ensamma om man ser till att slutkallelsen aldrig behöver anropas innan man gjort minst en yield.

Genomföranden i assemblerspråkRedigera

Maskinberoende assemblerspråk tillhandahåller ofta direkta metoder för coroutineexekvering. I MACRO-11, monteringsspråket för minidatorerna i PDP-11-familjen, utförs till exempel den ”klassiska” koroutinen genom instruktionen ”JSR PC,@(SP)+”, som hoppar till den adress som tagits upp från stapeln och lägger den aktuella (dvs. nästa) instruktionsadressen på stapeln. På VAXen (i Macro-32) är den jämförbara instruktionen ”JSB @(SP)+”. Till och med på en Motorola 6809 finns instruktionen ”JSR ”; notera ”++”, eftersom två byte (adress) tas upp från stacken. Denna instruktion används ofta i (standard) ”monitor” Assist 09.

.

Lämna en kommentar