Coroutine

A partire dal 2003, molti dei linguaggi di programmazione più popolari, compreso il C e i suoi derivati, non hanno un supporto diretto per le coroutine all’interno del linguaggio o delle loro librerie standard. Questo è, in gran parte, dovuto alle limitazioni dell’implementazione delle subroutine basate sullo stack. Un’eccezione è la libreria C++ Boost.Context, parte delle librerie boost, che supporta il context swapping su ARM, MIPS, PowerPC, SPARC e x86 su POSIX, Mac OS X e Windows. Le coroutine possono essere costruite su Boost.Context.

In situazioni in cui una coroutine sarebbe l’implementazione naturale di un meccanismo, ma non è disponibile, la risposta tipica è di usare una chiusura – una subroutine con variabili di stato (variabili statiche, spesso flag booleani) per mantenere uno stato interno tra le chiamate, e per trasferire il controllo al punto corretto. I condizionali all’interno del codice portano all’esecuzione di diversi percorsi di codice su chiamate successive, in base ai valori delle variabili di stato. Un’altra risposta tipica è quella di implementare una macchina a stati esplicita sotto forma di una grande e complessa dichiarazione di switch o attraverso una dichiarazione goto, in particolare un goto calcolato. Tali implementazioni sono considerate difficili da capire e mantenere, e una motivazione per il supporto delle coroutine.

I thread, e in misura minore le fibre, sono un’alternativa alle coroutine negli ambienti di programmazione mainstream oggi. I thread forniscono strutture per gestire l’interazione cooperativa in tempo reale di pezzi di codice in esecuzione simultanea. I thread sono ampiamente disponibili in ambienti che supportano il C (e sono supportati nativamente in molti altri linguaggi moderni), sono familiari a molti programmatori, e sono solitamente ben implementati, ben documentati e ben supportati. Tuttavia, poiché risolvono un problema grande e difficile, includono molte strutture potenti e complesse e hanno una curva di apprendimento corrispondentemente difficile. Come tale, quando una coroutine è tutto ciò che serve, usare un thread può essere eccessivo.

Un’importante differenza tra thread e coroutine è che i thread sono tipicamente programmati in anticipo mentre le coroutine no. Poiché i thread possono essere riprogrammati in qualsiasi momento e possono essere eseguiti simultaneamente, i programmi che usano i thread devono stare attenti al locking. Al contrario, poiché le coroutine possono essere riprogrammate solo in punti specifici del programma e non vengono eseguite simultaneamente, i programmi che usano le coroutine possono spesso evitare del tutto il blocco. Questa proprietà è anche citata come un vantaggio della programmazione event-driven o asincrona.

Siccome le fibre sono programmate in modo cooperativo, forniscono una base ideale per implementare le coroutine di cui sopra. Tuttavia, il supporto di sistema per le fibre è spesso carente rispetto a quello per i thread.

Implementazioni per CEdit

Per implementare coroutine di uso generale, è necessario ottenere un secondo stack di chiamate, che è una caratteristica non direttamente supportata dal linguaggio C. Un modo affidabile (anche se specifico della piattaforma) per ottenere questo è quello di utilizzare una piccola quantità di assembly in linea per manipolare esplicitamente il puntatore allo stack durante la creazione iniziale della coroutine. Questo è l’approccio raccomandato da Tom Duff in una discussione sui suoi meriti relativi rispetto al metodo usato da Protothreads. Sulle piattaforme che forniscono la chiamata di sistema POSIX sigaltstack, un secondo stack di chiamate può essere ottenuto chiamando una funzione springboard dall’interno di un gestore di segnali per raggiungere lo stesso obiettivo in C portatile, al costo di una certa complessità extra. Le librerie C conformi a POSIX o alla Single Unix Specification (SUSv3) fornivano routine come getcontext, setcontext, makecontext e swapcontext, ma queste funzioni sono state dichiarate obsolete in POSIX 1.2008.

Una volta ottenuto un secondo stack di chiamate con uno dei metodi elencati sopra, le funzioni setjmp e longjmp nella libreria C standard possono essere usate per implementare i passaggi tra coroutine. Queste funzioni salvano e ripristinano, rispettivamente, il puntatore allo stack, il contatore del programma, i registri salvati dalle chiamate, e qualsiasi altro stato interno come richiesto dall’ABI, in modo tale che il ritorno ad una coroutine dopo aver ceduto ripristina tutto lo stato che sarebbe stato ripristinato al ritorno da una chiamata di funzione. Implementazioni minimaliste, che non si appoggiano alle funzioni setjmp e longjmp, possono ottenere lo stesso risultato tramite un piccolo blocco di assemblaggio in linea che scambia solo il puntatore allo stack e il contatore del programma, e blocca tutti gli altri registri. Questo può essere significativamente più veloce, poiché setjmp e longjmp devono memorizzare in modo conservativo tutti i registri che possono essere in uso secondo l’ABI, mentre il metodo clobber permette al compilatore di memorizzare (riversando sullo stack) solo ciò che sa essere effettivamente in uso.

A causa della mancanza di supporto diretto del linguaggio, molti autori hanno scritto le proprie librerie per le coroutine che nascondono i dettagli di cui sopra. La libreria libtask di Russ Cox è un buon esempio di questo genere. Usa le funzioni di contesto se sono fornite dalla libreria C nativa; altrimenti fornisce le proprie implementazioni per ARM, PowerPC, Sparc e x86. Altre implementazioni degne di nota includono libpcl, coro, lthread, libCoroutine, libconcurrency, libcoro, ribs2, libdill, libaco e libco.

In aggiunta all’approccio generale di cui sopra, sono stati fatti diversi tentativi per approssimare le coroutine in C con combinazioni di subroutine e macro. Il contributo di Simon Tatham, basato sul dispositivo di Duff, è un notevole esempio del genere, ed è la base per Protothreads e implementazioni simili. Oltre alle obiezioni di Duff, i commenti dello stesso Tatham forniscono una valutazione franca dei limiti di questo approccio: “Per quanto ne so, questo è il peggior pezzo di hackery C mai visto in codice di produzione serio”. I principali difetti di questa approssimazione sono che, non mantenendo uno stack frame separato per ogni coroutine, le variabili locali non sono conservate attraverso le rese della funzione, non è possibile avere entrate multiple nella funzione, e il controllo può essere reso solo dalla routine di primo livello.

Implementazioni per C++Edit

  • C++ coroutines TS (Technical Specification), uno standard per estensioni del linguaggio C++ per un sottoinsieme senza stack di comportamenti simili alle coroutine, è in sviluppo. Visual C++ e Clang già supportano porzioni importanti nello spazio dei nomi std::experimental. coroutines Technical Specification
  • Boost.Coroutine – creato da Oliver Kowalke, è la libreria di coroutine portatile ufficialmente rilasciata di boost dalla versione 1.53. La libreria si basa su Boost.Context e supporta ARM, MIPS, PowerPC, SPARC e X86 su POSIX, Mac OS X e Windows.
  • Boost.Coroutine2 – anch’essa creata da Oliver Kowalke, è una libreria di coroutine portatile modernizzata da boost versione 1.59. Sfrutta le caratteristiche di C++11, ma rimuove il supporto per le coroutine simmetriche.
  • Mordor – Nel 2010, Mozy ha aperto una libreria C++ che implementa le coroutine, con un’enfasi sul loro utilizzo per astrarre l’I/O asincrono in un modello sequenziale più familiare.
  • CO2 – coroutine stackless basate su trucchi del preprocessore C++, che forniscono emulazione await/yield.
  • ScummVM – Il progetto ScummVM implementa una versione leggera di coroutine stackless basata sull’articolo di Simon Tatham.
  • tonbit::coroutine – implementazione di coroutine asimmetriche C +11 singolo .h tramite ucontext / fibra
  • Coroutines sbarcati in Clang nel maggio 2017, con implementazione libc++ in corso.
  • elle di Docker
  • oatpp-coroutines – coroutine stackless con programmazione progettata per operazioni di I/O ad alta concorrenza. Usato nell’esperimento dei 5 milioni di connessioni WebSocket di Oat++. Parte del framework web di Oat++.

Implementazioni per C#Edit

  • MindTouch Dream – Il framework MindTouch Dream REST fornisce un’implementazione di coroutines basata sul pattern iteratore C# 2.0
  • Caliburn – Il framework Caliburn screen patterns per WPF usa iteratori C# 2.0 per facilitare la programmazione UI, particolarmente in scenari asincroni.
  • Power Threading Library – La Power Threading Library di Jeffrey Richter implementa un AsyncEnumerator che fornisce un modello di programmazione asincrona semplificato usando coroutine basate su iteratori.
  • Il motore di gioco Unity implementa le coroutine.
  • Servelat Pieces – Il progetto Servelat Pieces di Yevhen Bobrov fornisce un’asincronia trasparente per servizi WCF Silverlight e la possibilità di chiamare asincronicamente qualsiasi metodo sincrono. L’implementazione è basata sull’iteratore Coroutines di Caliburn e sui blocchi iteratori di C#.
  • – Il Framework .NET 2.0+ fornisce ora funzionalità semi-coroutine (generatore) attraverso il pattern iterator e la parola chiave yield.

C# 5.0 include il supporto alla sintassi await.

Implementazioni per ClojureEdit

Cloroutine è una libreria di terze parti che fornisce supporto alle coroutine senza stack in Clojure. È implementata come una macro, dividendo staticamente un blocco di codice arbitrario su chiamate var arbitrarie ed emettendo la coroutine come una funzione stateful.

Implementazioni per DEdit

D implementa le coroutine come la sua classe di libreria standard Fiber Un generatore rende banale esporre una funzione fiber come intervallo di input, rendendo qualsiasi fiber compatibile con gli algoritmi di intervallo esistenti.

Implementazioni per JavaEdit

Ci sono diverse implementazioni di coroutine in Java. Nonostante i vincoli imposti dalle astrazioni di Java, la JVM non ne preclude la possibilità. Ci sono quattro metodi generali utilizzati, ma due rompono la portabilità del bytecode tra le JVM conformi agli standard.

  • JVM modificate. È possibile costruire una JVM modificata per supportare le coroutine in modo più nativo. La JVM Da Vinci ha avuto delle patch create.
  • Bytecode modificato. La funzionalità delle coroutine è possibile riscrivendo il normale bytecode Java, sia al volo che in fase di compilazione. I kit di strumenti includono Javaflow, Java Coroutines e Coroutines.
  • Meccanismi JNI specifici della piattaforma. Questi usano metodi JNI implementati nel sistema operativo o nelle librerie C per fornire le funzionalità alla JVM.
  • Astrazioni di thread. Le librerie di coroutine che sono implementate usando i thread possono essere pesanti, anche se le prestazioni variano in base all’implementazione dei thread della JVM.

Implementazioni in JavaScriptEdit

  • nodo-fibre
    • Fibjs – fibjs è un runtime JavaScript costruito sul motore JavaScript V8 di Chrome. fibjs utilizza fibers-switch, stile sync, e modello I/O non bloccante per costruire sistemi scalabili.
  • Da ECMAScript 2015, viene fornita la funzionalità di coroutine stackless attraverso “generatori” ed espressioni di resa.

Implementazioni per KotlinEdit

Kotlin implementa le coroutine come parte di una libreria first-party.

Implementazioni per Modula-2Edit

Modula-2 come definito da Wirth implementa le coroutine come parte della libreria standard SYSTEM.

La procedura NEWPROCESS() riempie un contesto dato un blocco di codice e spazio per uno stack come parametri, e la procedura TRANSFER() trasferisce il controllo a una coroutine dato il contesto della coroutine come suo parametro.

Implementazione in MonoEdit

Il Common Language Runtime di Mono ha il supporto per le continuazioni, da cui le coroutine possono essere costruite.

Implementazione nel .NET Framework come fibersEdit

Durante lo sviluppo del .NET Framework 2.0, Microsoft ha esteso il design delle API di hosting del Common Language Runtime (CLR) per gestire lo scheduling basato su fibre con un occhio al suo utilizzo in modalità fiber per SQL server. Prima del rilascio, il supporto per il gancio di commutazione dei compiti ICLRTask::SwitchOut è stato rimosso a causa di vincoli di tempo.Di conseguenza, l’uso dell’API di fibra per commutare i compiti non è attualmente un’opzione valida nel .NET Framework.

Implementazioni per PerlEdit

  • Coro

Le coroutine sono implementate nativamente in tutti i backend Raku.

Implementazioni per PHPEdit

  • Amphp
  • Coroutine implementate in un modo che ricorda le funzioni Python, e alcune Go, molti esempi che mostrano il codice convertito con lo stesso numero di linee e comportamento.

Implementazioni per PythonEdit

  • Python 2.5 implementa un migliore supporto per funzionalità simili alle coroutine, basato su generatori estesi (PEP 342)
  • Python 3.3 migliora questa capacità, supportando la delega ad un sottogeneratore (PEP 380)
  • Python 3.4 introduce un quadro completo di I/O asincrono come standardizzato in PEP 3156, che include coroutine che sfruttano la delega del sottogeneratore
  • Python 3.5 introduce un supporto esplicito per le coroutine con sintassi async/await (PEP 0492).
  • Da Python 3.7 async/await sono diventate parole chiave riservate.
  • Eventlet
  • Greenlet
  • gevent
  • stackless python
  • Abandoned
    • Shrapnel (ultimo rilascio 2015)
    • Kamaelia (ultimo rilascio 2010)
    • cogen (ultima release 2009)
    • multitask (ultima release 2007)
    • chiral

Implementazioni per RubyEdit

  • Ruby 1.9 supporta nativamente le coroutine che sono implementate come fibre, che sono semi-coroutine.
  • Un’implementazione di Marc De Scheemaecker
  • Ruby 2.5 e superiori supporta nativamente le coroutine che sono implementate come fibre
  • Un’implementazione di Thomas W Branson

Implementazioni per RustEdit

Rust supporta le coroutine dalla versione 1.39. C’è anche un runtime alternativo asincrono (progetto più vecchio del runtime standard di rust): tokio

Implementazioni per ScalaEdit

Scala Coroutines è un’implementazione di coroutine per Scala. Questa implementazione è un’estensione a livello di libreria che si basa sul sistema di macro Scala per trasformare staticamente sezioni del programma in oggetti coroutine. Come tale, questa implementazione non richiede modifiche nella JVM, quindi è completamente portabile tra diverse JVM e funziona con backend Scala alternativi, come Scala.js, che compila in JavaScript.

Scala Coroutines si basa sulla macro coroutine che trasforma un normale blocco di codice in una definizione di coroutine. Tale definizione di coroutine può essere invocata con l’operazione call, che istanzia un frame di coroutine. Un frame di coroutine può essere ripreso con il metodo resume, che riprende l’esecuzione del corpo della coroutine, fino a raggiungere una parola chiave yieldval, che sospende il frame di coroutine. Le coroutine Scala espongono anche un metodo snapshot, che duplica effettivamente la coroutine. Una descrizione dettagliata delle coroutine Scala con istantanee è apparsa a ECOOP 2018, insieme al loro modello formale.

Implementazioni per SchemeEdit

Siccome Scheme fornisce pieno supporto per le continuazioni, implementare le coroutine è quasi banale, richiedendo solo il mantenimento di una coda di continuazioni.

Implementazioni per SmalltalkEdit

Siccome, nella maggior parte degli ambienti Smalltalk, lo stack di esecuzione è un cittadino di prima classe, le coroutine possono essere implementate senza ulteriori librerie o supporto VM.

Implementazioni per SwiftEdit

  • SwiftCoroutine – libreria di coroutine Swift per iOS, macOS e Linux.

Implementazione per Tool Command Language (Tcl)Edit

Dalla versione 8.6, il Tool Command Language supporta le coroutine nel linguaggio di base.

Implementazioni per ValaEdit

Vala implementa il supporto nativo per le coroutine. Sono progettate per essere usate con un Gtk Main Loop, ma possono essere usate da sole se si fa attenzione a garantire che il callback finale non dovrà mai essere chiamato prima di fare almeno una resa.

Implementazioni in linguaggi assemblyModifica

I linguaggi assembly dipendenti dalla macchina spesso forniscono metodi diretti per l’esecuzione di coroutine. Per esempio, in MACRO-11, il linguaggio assembly della famiglia di minicomputer PDP-11, il “classico” passaggio di coroutine è effettuato dall’istruzione “JSR PC,@(SP)+”, che salta all’indirizzo saltato dallo stack e spinge l’indirizzo dell’istruzione corrente (cioè quella successiva) sullo stack. Su VAXen (in Macro-32) l’istruzione comparabile è “JSB @(SP)+”. Anche su un Motorola 6809 c’è l’istruzione “JSR “; si noti il “++”, poiché 2 byte (di indirizzo) vengono tirati fuori dallo stack. Questa istruzione è molto usata nel “monitor” (standard) Assist 09.

.

Lascia un commento