Coroutine

I 2003 havde mange af de mest populære programmeringssprog, herunder C og dets derivater, ikke direkte understøttelse af coroutines i sproget eller deres standardbiblioteker. Dette skyldes i vid udstrækning begrænsningerne ved stakbaseret implementering af underrutiner. En undtagelse er C++-biblioteket Boost.Context, der er en del af boost-bibliotekerne, og som understøtter context swapping på ARM, MIPS, PowerPC, SPARC og x86 på POSIX, Mac OS X og Windows. Coroutiner kan bygges på Boost.Context.

I situationer, hvor en coroutine ville være den naturlige implementering af en mekanisme, men ikke er tilgængelig, er det typiske svar at bruge en closure – en underrutine med tilstandsvariabler (statiske variabler, ofte boolske flag) til at opretholde en intern tilstand mellem kald og til at overføre kontrol til det rigtige punkt. Betingelser i koden resulterer i, at der udføres forskellige kodeveje ved på hinanden følgende kald, baseret på værdierne af tilstandsvariablerne. Et andet typisk svar er at implementere en eksplicit tilstandsmaskine i form af en stor og kompleks switch-anvisning eller via en goto-anvisning, især en beregnet goto-anvisning. Sådanne implementeringer anses for at være vanskelige at forstå og vedligeholde og er en motivation for understøttelse af coroutiner.

Threads og i mindre grad fibers er i dag et alternativ til coroutiner i almindelige programmeringsmiljøer. Tråde giver faciliteter til styring af den kooperative interaktion i realtid mellem samtidig udførende kodestykker. Tråde er bredt tilgængelige i miljøer, der understøtter C (og understøttes nativt i mange andre moderne sprog), er velkendte for mange programmører og er normalt velimplementerede, veldokumenterede og velunderstøttede. Da de løser et stort og vanskeligt problem, omfatter de imidlertid mange kraftfulde og komplekse faciliteter og har en tilsvarende vanskelig indlæringskurve. Når der kun er brug for en coroutine, kan det derfor være overkill at bruge en tråd.

En vigtig forskel mellem tråde og coroutines er, at tråde typisk er præemptivt planlagt, mens coroutines ikke er det. Fordi tråde kan omplanlægges når som helst og kan udføres samtidig, skal programmer, der bruger tråde, være forsigtige med låsning. Da coroutines derimod kun kan omplanlægges på bestemte tidspunkter i programmet og ikke udføres samtidig, kan programmer, der anvender coroutines, ofte helt undgå låsning. Denne egenskab nævnes også som en fordel ved begivenhedsstyret eller asynkron programmering.

Da fibre er planlagt i samarbejde, udgør de et ideelt grundlag for implementering af ovenstående coroutiner. Systemunderstøttelse for fibre mangler dog ofte i forhold til systemunderstøttelse for tråde.

Implementeringer til CEdit

For at implementere generelle coroutiner skal der skaffes en anden kaldestak, hvilket er en funktion, der ikke understøttes direkte af C-sproget. En pålidelig (om end platformsspecifik) måde at opnå dette på er at bruge en lille mængde inline-assemblering til eksplicit at manipulere stak-pointeren under den indledende oprettelse af coroutinen. Det er den fremgangsmåde, der anbefales af Tom Duff i en diskussion om dens relative fordele i forhold til den metode, der anvendes af Protothreads. På platforme, der leverer POSIX-systemkaldet sigaltstack, kan man få en anden callstack ved at kalde en springboard-funktion fra en signalbehandler for at opnå det samme mål i bærbar C på bekostning af en vis ekstra kompleksitet. C-biblioteker, der overholder POSIX eller Single Unix Specification (SUSv3), indeholdt rutiner som getcontext, setcontext, makecontext og swapcontext, men disse funktioner blev erklæret forældede i POSIX 1.2008.

Når en anden call stack er opnået med en af de ovenfor nævnte metoder, kan setjmp- og longjmp-funktionerne i standard C-biblioteket derefter anvendes til at implementere skift mellem coroutiner. Disse funktioner gemmer og genopretter henholdsvis stakpointeren, programtælleren, de kaldereserverede registre og enhver anden intern tilstand som krævet af ABI’en, således at tilbagevenden til en coroutine efter at have givet afkast genopretter al den tilstand, der ville blive genoprettet ved tilbagevenden fra et funktionskald. Minimalistiske implementeringer, som ikke er baseret på setjmp- og longjmp-funktionerne, kan opnå det samme resultat ved hjælp af en lille blok inline-assemblering, som blot bytter stackpointeren og programtælleren og gemmer alle andre registre. Dette kan være betydeligt hurtigere, da setjmp og longjmp konservativt skal gemme alle registre, der kan være i brug ifølge ABI’en, mens clobber-metoden giver compileren mulighed for kun at gemme (ved at spilde til stakken), hvad den ved, at der faktisk er i brug.

På grund af manglen på direkte sprogunderstøttelse har mange forfattere skrevet deres egne biblioteker til coroutiner, som skjuler ovenstående detaljer. Russ Cox’ libtask-bibliotek er et godt eksempel på denne genre. Det bruger kontekstfunktionerne, hvis de leveres af det native C-bibliotek; ellers leverer det sine egne implementeringer til ARM, PowerPC, Sparc og x86. Andre bemærkelsesværdige implementeringer omfatter libpcl, coro, lthread, libCoroutine, libconcurrency, libcoro, ribs2, libdill., libaco og libco.

Ud over den generelle fremgangsmåde ovenfor er der gjort flere forsøg på at tilnærme coroutiner i C med kombinationer af underrutiner og makroer. Simon Tathams bidrag, der er baseret på Duff’s device, er et bemærkelsesværdigt eksempel på genren og danner grundlaget for Protothreads og lignende implementeringer. Ud over Duffs indvendinger giver Tathams egne kommentarer en åbenhjertig vurdering af begrænsningerne ved denne fremgangsmåde: “Så vidt jeg ved, er dette det værste stykke C-hackeri, der nogensinde er set i seriøs produktionskode.” De væsentligste mangler ved denne tilnærmelse er, at lokale variabler ikke bevares på tværs af afgivelser fra funktionen, da der ikke opretholdes en separat stakramme for hver coroutine, det er ikke muligt at have flere indgange til funktionen, og kontrollen kan kun afgives fra rutinen på øverste niveau.

Implementeringer til C++Edit

  • C++ coroutines TS (Technical Specification), en standard for C++-sprogudvidelser til en stakfri delmængde af coroutine-lignende adfærd, er under udvikling. Visual C++ og Clang understøtter allerede store dele i std::experimental namespace. coroutines Technical Specification
  • Boost.Coroutine – skabt af Oliver Kowalke, er det officielle frigivne bærbare coroutine-bibliotek for boost siden version 1.53. Biblioteket er baseret på Boost.Context og understøtter ARM, MIPS, PowerPC, SPARC og X86 på POSIX, Mac OS X og Windows.
  • Boost.Coroutine2 – også oprettet af Oliver Kowalke, er et moderniseret bærbart coroutinebibliotek siden boost version 1.59. Det udnytter C++11-funktioner, men fjerner understøttelsen af symmetriske coroutiner.
  • Mordor – I 2010 open sourceede Mozy et C++-bibliotek, der implementerer coroutiner, med vægt på at bruge dem til at abstrahere asynkron I/O til en mere velkendt sekventiel model.
  • CO2 – stackless coroutine baseret på C++ præprocessortricks, der giver await/yield-emulering.
  • ScummVM – ScummVM-projektet implementerer en letvægtsversion af stackless coroutines baseret på Simon Tathams artikel.
  • tonbit::coroutine – C++11 single .h asymmetrisk coroutine-implementering via ucontext / fiber
  • Coroutines landede i Clang i maj 2017, med libc++-implementering i gang.
  • elle by Docker
  • oatpp-coroutines – stackless coroutines med skemalægning designet til I/O-operationer på højkonurrency-niveau. Anvendt i eksperimentet 5-million WebSocket-forbindelser af Oat++. En del af Oat++ webrammen.

Implementeringer til C#Edit

  • MindTouch Dream – MindTouch Dream REST-rammen indeholder en implementering af coroutiner baseret på C# 2.0-iteratormønsteret
  • Caliburn – Caliburn-rammen for skærmmønstre til WPF bruger C# 2.0-iteratorer til at lette UI-programmering, især i asynkrone scenarier.
  • Power Threading Library – Power Threading Library af Jeffrey Richter implementerer en AsyncEnumerator, der giver en forenklet asynkron programmeringsmodel ved hjælp af iteratorbaserede coroutines.
  • Unity-spilmotoren implementerer coroutines.
  • Servelat Pieces – Servelat Pieces-projektet af Yevhen Bobrov giver gennemsigtig asynkronitet for Silverlight WCF-tjenester og mulighed for asynkront at kalde enhver synkron metode. Implementeringen er baseret på Caliburns Coroutines-iterator og C#-iteratorblokke.
  • – .NET 2.0+ Framework giver nu semi-coroutine (generator)-funktionalitet gennem iteratormønsteret og yield-keywordet.

C## 5.0 indeholder understøttelse af await-syntaks.

Implementeringer til ClojureEdit

Cloroutine er et bibliotek fra tredjepart, der giver understøttelse for stackless coroutines i Clojure. Det er implementeret som en makro, der statisk opdeler en vilkårlig kodeblok på vilkårlige var-opkald og udsender coroutinen som en stateful funktion.

Implementeringer til DEdit

D implementerer coroutiner som sin standardbiblioteksklasse Fiber En generator gør det trivielt at eksponere en fiberfunktion som et inputområde, hvilket gør enhver fiber kompatibel med eksisterende intervalalgoritmer.

Implementeringer til JavaEdit

Der findes flere implementeringer til coroutiner i Java. På trods af de begrænsninger, der er pålagt af Javas abstraktioner, udelukker JVM’en ikke muligheden. Der anvendes fire generelle metoder, men to af dem bryder bytekodeportabiliteten blandt standardkonforme JVM’er.

  • Modificerede JVM’er. Det er muligt at opbygge en patched JVM for at understøtte coroutiner mere nativt. Da Vinci JVM’en har fået lavet patches.
  • Modificeret bytekode. Coroutine-funktionalitet er mulig ved at omskrive almindelig Java-bytekode, enten i farten eller på kompileringstidspunktet. Værktøjssæt omfatter Javaflow, Java Coroutines og Coroutines.
  • Platformspecifikke JNI-mekanismer. Disse bruger JNI-metoder, der er implementeret i OS- eller C-bibliotekerne, til at levere funktionaliteten til JVM’en.
  • Thread-abstraktioner. Coroutine-biblioteker, der er implementeret ved hjælp af tråde, kan være tunge, selv om ydeevnen varierer afhængigt af JVM’ens trådimplementering.

Implementeringer i JavaScriptEdit

  • node-fibers
    • Fibjs – fibjs er en JavaScript-køringstid, der er bygget på Chromes V8 JavaScript-motor. fibjs bruger fibers-switch, sync-stil og non-blocking I/O-model til at opbygge skalerbare systemer.
  • Siden ECMAScript 2015 er der stackless coroutine-funktionalitet gennem “generatorer” og yield-udtryk.

Implementeringer til KotlinEdit

Kotlin implementerer coroutiner som en del af et førstepartsbibliotek.

Implementeringer til Modula-2Edit

Modula-2 som defineret af Wirth implementerer coroutiner som en del af standardbiblioteket SYSTEM.

Proceduren NEWPROCESS() udfylder en kontekst givet en kodeblok og plads til en stak som parametre, og proceduren TRANSFER() overfører kontrol til en coroutine givet coroutinens kontekst som parameter.

Implementering i MonoEdit

Mono Common Language Runtime har understøttelse af fortsættelser, hvorfra der kan opbygges coroutiner.

Implementering i .NET Framework som fibersEdit

Under udviklingen af .NET Framework 2.0 udvidede Microsoft designet af Common Language Runtime (CLR) hosting API’er til at håndtere fiberbaseret planlægning med henblik på anvendelse i fiber-mode til SQL server. Før udgivelsen blev understøttelsen af opgaveskift-krogen ICLRTask::SwitchOut fjernet på grund af tidsbegrænsninger. derfor er brugen af fiber-API’en til at skifte opgaver i øjeblikket ikke en levedygtig mulighed i .NET Framework.

Implementeringer til PerlEdit

  • Coro

Coroutiner er nativt implementeret i alle Raku-backends.

Implementeringer til PHPEdit

  • Amphp
  • Coroutine implementeret på en måde, der ligner Python-funktioner, og nogle Go, mange eksempler, der viser der kode konverteret med samme antal linjer og adfærd.

Implementeringer til PythonEdit

  • Python 2.5 implementerer bedre understøttelse for coroutine-lignende funktionalitet, baseret på udvidede generatorer (PEP 342)
  • Python 3.3 forbedrer denne evne ved at understøtte delegering til en undergenerator (PEP 380)
  • Python 3.4 introducerer en omfattende asynkron I/O-ramme som standardiseret i PEP 3156, som omfatter coroutiner, der udnytter delegering til en undergenerator
  • Python 3.5 introducerer eksplicit understøttelse af coroutiner med async/await-syntaks (PEP 0492).
  • Siden Python 3.7 er async/await blevet reserverede nøgleord .
  • Eventlet
  • Greenlet
  • gevent
  • stackless python
  • Abandoned
    • Shrapnel (sidste udgivelse 2015)
    • Kamaelia (sidste udgivelse 2010)
    • cogen (sidste udgivelse 2009)
    • multitask (sidste udgivelse 2007)
    • chiral

Implementeringer til RubyEdit

  • Ruby 1.9 understøtter coroutiner nativt, der er implementeret som fibre, som er semi-coroutiner.
  • En implementering af Marc De Scheemaecker
  • Ruby 2.5 og højere understøtter coroutines nativt, som er implementeret som fibers
  • En implementering af Thomas W Branson

Implementeringer til RustEdit

Rust understøtter coroutines siden version 1.39 .Der findes også en alternativ asynkron køretid (ældre projekt end standardkøretiden for rust) : tokio

Implementeringer til ScalaEdit

Scala Coroutines er en coroutine-implementering til Scala. Denne implementering er en udvidelse på biblioteksniveau, der er afhængig af Scala-makrosystemet til statisk at omdanne dele af programmet til coroutineobjekter. Som sådan kræver denne implementering ikke ændringer i JVM’en, så den er fuldt overførbar mellem forskellige JVM’er og fungerer med alternative Scala-baggrunde, såsom Scala.js, som kompilerer til JavaScript.

Scala Coroutines er afhængig af makroen coroutine, der omdanner en normal kodeblok til en coroutine-definition. En sådan coroutine-definition kan påkaldes med call-operationen, som instantierer en coroutine-ramme. En coroutine-ramme kan genoptages med resume-metoden, som genoptager udførelsen af coroutinens krop, indtil man når frem til et yieldval-nøgleord, som suspenderer coroutine-rammen. Scala-coroutiner eksponerer også en snapshot-metode, som effektivt duplikerer coroutinen. En detaljeret beskrivelse af Scala-coroutiner med snapshots dukkede op på ECOOP 2018 sammen med deres formelle model.

Implementeringer til SchemeEdit

Da Scheme giver fuld understøttelse for fortsættelser, er det næsten trivielt at implementere coroutiner, idet det kun er nødvendigt at vedligeholde en kø af fortsættelser.

Implementeringer til SmalltalkEdit

Da eksekveringsstacken i de fleste Smalltalk-miljøer er en førsteklasses borger, kan coroutiner implementeres uden yderligere biblioteks- eller VM-understøttelse.

Implementeringer til SwiftEdit

  • SwiftCoroutine – Swift-coroutinebibliotek til iOS, macOS og Linux.

Implementering til Tool Command Language (Tcl)Edit

Siden version 8.6 understøtter Tool Command Language coroutines i kernesproget.

Implementeringer til ValaEdit

Vala implementerer native understøttelse af coroutines. De er designet til at blive brugt sammen med et Gtk Main Loop, men kan bruges alene, hvis man sørger for at sikre, at end callback aldrig skal kaldes, før man har udført mindst én yield.

Implementeringer i assembler-sprogRediger

Maskinafhængige assembler-sprog indeholder ofte direkte metoder til coroutine-eksekvering. I MACRO-11, assemblagesproget for PDP-11-familien af minicomputere, udføres det “klassiske” coroutine-skift f.eks. ved hjælp af instruktionen “JSR PC,@(SP)+”, som hopper til den adresse, der er taget fra stakken, og lægger den aktuelle (dvs. den næste) instruktionsadresse på stakken. På VAXen (i Macro-32) er den tilsvarende instruktion “JSB @(SP)+”. Selv på en Motorola 6809 er der instruktionen “JSR “; bemærk “++”, da 2 bytes (af adressen) poppes fra stakken. Denne instruktion er meget brugt i (standard) “monitor” Assist 09.

Skriv en kommentar