Coroutine

Vanaf 2003 hebben veel van de populairste programmeertalen, waaronder C en zijn afgeleiden, geen directe ondersteuning voor coroutines binnen de taal of hun standaardbibliotheken. Dit is, voor een groot deel, te wijten aan de beperkingen van stack-gebaseerde subroutine implementatie. Een uitzondering is de C++ bibliotheek Boost.Context, onderdeel van boost libraries, die context swapping ondersteunt op ARM, MIPS, PowerPC, SPARC en x86 op POSIX, Mac OS X en Windows. Coroutines kunnen worden gebouwd op Boost.Context.

In situaties waar een coroutine de natuurlijke implementatie van een mechanisme zou zijn, maar niet beschikbaar is, is de typische reactie het gebruik van een closure – een subroutine met toestandsvariabelen (statische variabelen, vaak booleaanse vlaggen) om een interne toestand te behouden tussen aanroepen, en om controle over te dragen naar het juiste punt. Conditionals binnen de code resulteren in de uitvoering van verschillende codepaden bij opeenvolgende aanroepen, gebaseerd op de waarden van de toestandsvariabelen. Een andere typische reactie is het implementeren van een expliciete toestandsmachine in de vorm van een grote en complexe switch-statement of via een goto-statement, met name een berekende goto. Dergelijke implementaties worden beschouwd als moeilijk te begrijpen en te onderhouden, en een motivatie voor coroutine ondersteuning.

Threads, en in mindere mate fibers, zijn een alternatief voor coroutines in mainstream programmeeromgevingen vandaag de dag. Threads bieden faciliteiten voor het beheren van de realtime coöperatieve interactie van gelijktijdig uitvoerende stukken code. Threads zijn ruim beschikbaar in omgevingen die C ondersteunen (en worden van nature ondersteund in veel andere moderne talen), zijn bekend bij veel programmeurs, en zijn meestal goed geïmplementeerd, goed gedocumenteerd en goed ondersteund. Maar omdat ze een groot en moeilijk probleem oplossen, bevatten ze veel krachtige en complexe faciliteiten en hebben ze een navenant moeilijke leercurve. Als zodanig, wanneer een coroutine alles is wat nodig is, kan het gebruik van een thread overkill zijn.

Een belangrijk verschil tussen threads en coroutines is dat threads typisch preemptief worden gepland, terwijl coroutines dat niet zijn. Omdat threads op elk moment opnieuw kunnen worden ingepland en gelijktijdig kunnen worden uitgevoerd, moeten programma’s die threads gebruiken voorzichtig zijn met locken. Omdat coroutines daarentegen alleen op specifieke punten in het programma opnieuw gepland kunnen worden en niet gelijktijdig worden uitgevoerd, kunnen programma’s die coroutines gebruiken vaak vergrendeling geheel vermijden. Deze eigenschap wordt ook wel genoemd als een voordeel van event-driven of asynchroon programmeren.

Omdat fibers coöperatief worden gepland, bieden ze een ideale basis voor het implementeren van coroutines hierboven. Systeemondersteuning voor fibers ontbreekt echter vaak in vergelijking met die voor threads.

Implementaties voor CEdit

Om general-purpose coroutines te implementeren, moet een tweede aanroep-stack worden verkregen, een eigenschap die niet direct door de C taal wordt ondersteund. Een betrouwbare (zij het platform-specifieke) manier om dit te bereiken is door een kleine hoeveelheid inline assembly te gebruiken om expliciet de stack pointer te manipuleren tijdens de initiële creatie van de coroutine. Dit is de aanpak die wordt aanbevolen door Tom Duff in een discussie over de relatieve verdiensten ten opzichte van de methode die wordt gebruikt door Protothreads. Op platformen die de POSIX sigaltstack system call bieden, kan een tweede aanroep-stack worden verkregen door een springplank-functie aan te roepen vanuit een signaal-handler om hetzelfde doel te bereiken in portable C, ten koste van enige extra complexiteit. C bibliotheken die voldoen aan POSIX of de Single Unix Specification (SUSv3) boden routines als getcontext, setcontext, makecontext en swapcontext, maar deze functies zijn in POSIX 1.2008 verouderd verklaard.

Als eenmaal een tweede aanroep-stack is verkregen met een van de hierboven genoemde methoden, kunnen de setjmp en longjmp functies in de standaard C bibliotheek vervolgens worden gebruikt om de wisselingen tussen coroutines te implementeren. Deze functies bewaren en herstellen respectievelijk de stack pointer, program counter, callee-saved registers, en elke andere interne toestand zoals vereist door de ABI, zodanig dat terugkeren naar een coroutine na te hebben opgeleverd alle toestand herstelt die hersteld zou worden bij het terugkeren van een functie-aanroep. Minimalistische implementaties, die niet meeliften op de setjmp en longjmp functies, kunnen hetzelfde resultaat bereiken via een klein blok inline assembly dat alleen de stack pointer en program counter verwisselt, en alle andere registers afschermt. Dit kan aanzienlijk sneller zijn, omdat setjmp en longjmp conservatief alle registers moeten opslaan die volgens de ABI in gebruik kunnen zijn, terwijl de clobber-methode de compiler toestaat om (door naar de stack te morsen) alleen op te slaan wat het weet dat werkelijk in gebruik is.

Wegens het gebrek aan directe taalondersteuning hebben veel auteurs hun eigen bibliotheken voor coroutines geschreven die de bovenstaande details verbergen. Russ Cox’s libtask bibliotheek is een goed voorbeeld van dit genre. Het gebruikt de context functies als ze worden geleverd door de native C bibliotheek; anders biedt het zijn eigen implementaties voor ARM, PowerPC, Sparc, en x86. Andere opmerkelijke implementaties zijn libpcl, coro, lthread, libCoroutine, libconcurrency, libcoro, ribs2, libdill., libaco, en libco.

Naast de algemene benadering hierboven, zijn er verschillende pogingen gedaan om coroutines in C te benaderen met combinaties van subroutines en macro’s. Simon Tatham’s bijdrage, gebaseerd op Duff’s apparaat, is een opmerkelijk voorbeeld van het genre, en is de basis voor Protothreads en soortgelijke implementaties. Naast Duff’s bezwaren, geeft Tatham’s eigen commentaar een openhartige evaluatie van de beperkingen van deze benadering: “Voor zover ik weet, is dit het slechtste stukje C hackery dat ooit in serieuze productiecode is gezien.” De belangrijkste tekortkomingen van deze benadering zijn dat, door het niet handhaven van een apart stack frame voor elke coroutine, lokale variabelen niet bewaard blijven over yields van de functie, het is niet mogelijk om meerdere entries naar de functie te hebben, en controle kan alleen worden overgedragen van de top-level routine.

Implementaties voor C++Edit

  • C++ coroutines TS (Technical Specification), een standaard voor C++ taaluitbreidingen voor een stackless subset van coroutine-achtig gedrag, is in ontwikkeling. Visual C++ en Clang ondersteunen al grote delen in de std::experimental namespace. coroutines Technical Specification
  • Boost.Coroutine – gemaakt door Oliver Kowalke, is de officieel uitgebrachte portable coroutine library van boost sinds versie 1.53. De bibliotheek is gebaseerd op Boost.Context en ondersteunt ARM, MIPS, PowerPC, SPARC en X86 op POSIX, Mac OS X en Windows.
  • Boost.Coroutine2 – ook gemaakt door Oliver Kowalke, is een gemoderniseerde portable coroutine bibliotheek sinds boost versie 1.59. Het maakt gebruik van C++11 functies, maar verwijdert de ondersteuning voor symmetrische coroutines.
  • Mordor – In 2010, Mozy open sourced een C++ bibliotheek implementeren coroutines, met de nadruk op het gebruik ervan om asynchrone I / O abstraheren in een meer vertrouwd sequentieel model.
  • CO2 – stackless coroutine gebaseerd op C++ preprocessor trucs, die await/yield emulatie biedt.
  • ScummVM – Het ScummVM project implementeert een lichtgewicht versie van stackless coroutines gebaseerd op Simon Tatham’s artikel.
  • tonbit::coroutine – C++11 single .h asymmetrische coroutine-implementatie via ucontext / fiber
  • Coroutines landde in Clang in mei 2017, met libc++ implementatie aan de gang.
  • elle door Docker
  • oatpp-coroutines – stackless coroutines met scheduling ontworpen voor I / O-bewerkingen met hoge concurrency-niveau. Gebruikt in het 5-miljoen WebSocket verbindingen experiment van Oat++. Onderdeel van het Oat++ web framework.

Implementaties voor C#Edit

  • MindTouch Dream – Het MindTouch Dream REST framework biedt een implementatie van coroutines gebaseerd op het C# 2.0 iterator pattern
  • Caliburn – Het Caliburn schermpatronen framework voor WPF gebruikt C# 2.0 iterators om UI programmering te vergemakkelijken, met name in asynchrone scenario’s.
  • Power Threading Library – De Power Threading Library van Jeffrey Richter implementeert een AsyncEnumerator die een vereenvoudigd Asynchronous Programming Model biedt met behulp van iterator-gebaseerde coroutines.
  • De Unity game engine implementeert coroutines.
  • Servelat Pieces – Het Servelat Pieces project van Yevhen Bobrov biedt transparante asynchronie voor Silverlight WCF services en de mogelijkheid om asynchroon elke synchrone methode aan te roepen. De implementatie is gebaseerd op Caliburn’s Coroutines iterator en C# iterator blocks.
  • – Het .NET 2.0+ Framework biedt nu semi-coroutine (generator) functionaliteit via het iterator patroon en yield keyword.

C# 5.0 bevat await syntax ondersteuning.

Implementaties voor ClojureEdit

Cloroutine is een third-party library die ondersteuning biedt voor stackless coroutines in Clojure. Het is geïmplementeerd als een macro, die statisch een willekeurig codeblok splitst op willekeurige var aanroepen en de coroutine als een stateful functie emitteert.

Implementaties voor DEdit

D implementeert coroutines als zijn standaard bibliotheek klasse Fiber Een generator maakt het triviaal om een fiber functie bloot te stellen als een input range, waardoor elke fiber compatibel is met bestaande range algoritmen.

Implementaties voor JavaEdit

Er zijn verschillende implementaties voor coroutines in Java. Ondanks de beperkingen die de abstracties van Java opleggen, sluit de JVM de mogelijkheid niet uit. Er worden vier algemene methoden gebruikt, maar twee daarvan verbreken de bytecodeportabiliteit tussen JVM’s die aan de standaarden voldoen.

  • Gewijzigde JVM’s. Het is mogelijk om een gepatchte JVM te bouwen om coroutines beter te ondersteunen. De Da Vinci JVM heeft patches laten maken.
  • Gewijzigde bytecode. Coroutine functionaliteit is mogelijk door het herschrijven van normale Java bytecode, hetzij on the fly of bij het compileren. Toolkits zijn onder meer Javaflow, Java Coroutines, en Coroutines.
  • Platform-specifieke JNI-mechanismen. Deze maken gebruik van JNI methoden die zijn geïmplementeerd in het OS of C bibliotheken om de functionaliteit te bieden aan de JVM.
  • Thread-abstracties. Coroutine bibliotheken die zijn geïmplementeerd met behulp van threads kunnen zwaar zijn, hoewel de prestaties zullen variëren op basis van de JVM’s thread implementatie.

Implementaties in JavaScriptEdit

  • node-fibers
    • Fibjs – fibjs is een JavaScript runtime gebouwd op Chrome’s V8 JavaScript engine. fibjs gebruikt fibers-switch, sync-stijl en niet-blokkerende I/O-model om schaalbare systemen te bouwen.
  • Sinds ECMAScript 2015 wordt stackless coroutine-functionaliteit via “generators” en yield expressies geboden.

Implementaties voor KotlinEdit

Kotlin implementeert coroutines als onderdeel van een first-party bibliotheek.

Implementaties voor Modula-2Edit

Modula-2 zoals gedefinieerd door Wirth implementeert coroutines als onderdeel van de standaard SYSTEM-bibliotheek.

De procedure NEWPROCESS() vult een context gegeven een codeblok en ruimte voor een stack als parameters, en de procedure TRANSFER() brengt de controle over naar een coroutine gegeven de context van de coroutine als parameter.

Implementatie in MonoEdit

De Mono Common Language Runtime heeft ondersteuning voor continuaties, waaruit coroutines kunnen worden gebouwd.

Implementatie in het .NET Framework als fibersEdit

Tijdens de ontwikkeling van het .NET Framework 2.0, breidde Microsoft het ontwerp van de Common Language Runtime (CLR) hosting API’s uit om fiber-gebaseerde scheduling af te handelen met het oog op het gebruik ervan in fiber-mode voor SQL-server. Vóór de release werd de ondersteuning voor de task switching hook ICLRTask::SwitchOut verwijderd als gevolg van tijdsbeperkingen.Dientengevolge is het gebruik van de fiber API om taken te wisselen momenteel geen haalbare optie in het .NET Framework.

Implementaties voor PerlEdit

  • Coro

Coroutines zijn van nature geïmplementeerd in alle Raku backends.

Implementaties voor PHPEdit

  • Amphp
  • Coroutine geïmplementeerd op een manier die lijkt op Python functies, en sommige Go, veel voorbeelden tonen er code omgezet met hetzelfde aantal regels en gedrag.

Implementaties voor PythonEdit

  • Python 2.5 implementeert betere ondersteuning voor coroutine-achtige functionaliteit, gebaseerd op extended generators (PEP 342)
  • Python 3.3 verbetert deze mogelijkheid, door delegatie naar een subgenerator te ondersteunen (PEP 380)
  • Python 3.4 introduceert een uitgebreid asynchroon I/O raamwerk zoals gestandaardiseerd in PEP 3156, dat coroutines bevat die gebruik maken van subgenerator delegatie
  • Python 3.5 introduceert expliciete ondersteuning voor coroutines met async/await syntaxis (PEP 0492).
  • Sinds Python 3.7 zijn async/await gereserveerde sleutelwoorden geworden .
  • Eventlet
  • Greenlet
  • gevent
  • stackless python
  • Abandoned
    • Shrapnel (laatste release 2015)
    • Kamaelia (laatste release 2010)
    • cogen (laatste release 2009)
    • multitask (laatste release 2007)
    • chiral

Implementaties voor RubyEdit

  • Ruby 1.9 ondersteunt van nature coroutines die zijn geïmplementeerd als fibers, die semi-coroutines zijn.
  • Een implementatie door Marc De Scheemaecker
  • Ruby 2.5 en hoger ondersteunt van nature coroutines die zijn geïmplementeerd als fibers
  • Een implementatie door Thomas W Branson

Implementaties voor RustEdit

Rust ondersteunt coroutines sinds versie 1.39 .Er is ook een alternatieve asynchrone runtime (ouder project dan de standaard runtime van rust) : tokio

Implementaties voor ScalaEdit

Scala Coroutines is een coroutine implementatie voor Scala. Deze implementatie is een uitbreiding op bibliotheekniveau die vertrouwt op het Scala macro systeem om statisch delen van het programma om te zetten in coroutine objecten. Als zodanig vereist deze implementatie geen wijzigingen in de JVM, dus het is volledig overdraagbaar tussen verschillende JVM’s en werkt met alternatieve Scala backends, zoals Scala.js, die compileert naar JavaScript.

Scala Coroutines vertrouwen op de coroutine macro die een normaal blok code transformeert in een coroutine definitie. Zo’n coroutinedefinitie kan worden aangeroepen met de bewerking call, die een coroutineframe installeert. Een coroutine frame kan worden hervat met de resume methode, die de uitvoering van de body van de coroutine hervat, tot het bereiken van een yieldval sleutelwoord, dat het coroutine frame opschort. Scala Coroutines laten ook een snapshot methode zien, die effectief de coroutine dupliceert. Een gedetailleerde beschrijving van Scala coroutines met snapshots verscheen op ECOOP 2018, samen met hun formele model.

Implementaties voor SchemeEdit

Omdat Scheme volledige ondersteuning voor voortzettingen biedt, is het implementeren van coroutines bijna triviaal, waarbij alleen een wachtrij van voortzettingen hoeft te worden onderhouden.

Implementaties voor SmalltalkEdit

Omdat in de meeste Smalltalk-omgevingen de execution stack een eersterangs burger is, kunnen coroutines worden geïmplementeerd zonder extra bibliotheek of VM-ondersteuning.

Implementaties voor SwiftEdit

  • SwiftCoroutine – Swift-coroutines-bibliotheek voor iOS, macOS en Linux.

Implementatie voor Tool Command Language (Tcl)Edit

Sinds versie 8.6 ondersteunt de Tool Command Language coroutines in de kerntaal.

Implementaties voor ValaEdit

Vala implementeert native ondersteuning voor coroutines. Ze zijn ontworpen om te worden gebruikt met een Gtk Main Loop, maar kan alleen worden gebruikt als zorg wordt besteed om ervoor te zorgen dat het einde callback zal nooit moeten worden aangeroepen voordat het doen van, ten minste, een yield.

Implementaties in assemblagetalenEdit

Machine-afhankelijke assemblagetalen bieden vaak directe methoden voor coroutine uitvoering. In MACRO-11 bijvoorbeeld, de assembleertaal van de PDP-11 familie van minicomputers, wordt de “klassieke” coroutineswitch bewerkstelligd door de instructie “JSR PC,@(SP)+”, die naar het adres springt dat van de stack is gepopt en het huidige (d.w.z. dat van het volgende) instructieadres op de stack duwt. Op VAXen (in Macro-32) is de vergelijkbare instructie “JSB @(SP)+”. Zelfs op een Motorola 6809 is er de instructie “JSR “; let op de “++”, want er worden 2 bytes (van het adres) van de stack gepopt. Deze instructie wordt veel gebruikt in de (standaard) “monitor” Assist 09.

Plaats een reactie