Coroutine

En 2003, un grand nombre des langages de programmation les plus populaires, y compris le C et ses dérivés, n’ont pas de support direct pour les coroutines dans le langage ou leurs bibliothèques standard. Ceci est, en grande partie, dû aux limitations de l’implémentation des sous-routines basées sur la pile. Une exception est la bibliothèque C++ Boost.Context, qui fait partie des bibliothèques Boost et qui supporte le changement de contexte sur ARM, MIPS, PowerPC, SPARC et x86 sur POSIX, Mac OS X et Windows. Les coroutines peuvent être construites sur Boost.Context.

Dans les situations où une coroutine serait l’implémentation naturelle d’un mécanisme, mais n’est pas disponible, la réponse typique est d’utiliser une fermeture – une sous-routine avec des variables d’état (variables statiques, souvent des drapeaux booléens) pour maintenir un état interne entre les appels, et pour transférer le contrôle au point correct. Les conditionnels dans le code entraînent l’exécution de différents chemins de code lors d’appels successifs, en fonction des valeurs des variables d’état. Une autre réponse typique consiste à mettre en œuvre un automate d’état explicite sous la forme d’une instruction switch large et complexe ou via une instruction goto, en particulier un goto calculé. De telles implémentations sont considérées comme difficiles à comprendre et à maintenir, et une motivation pour le support des coroutines.

Les threads, et dans une moindre mesure les fibres, sont une alternative aux coroutines dans les environnements de programmation courants aujourd’hui. Les threads fournissent des facilités pour gérer l’interaction coopérative en temps réel de morceaux de code s’exécutant simultanément. Les threads sont largement disponibles dans les environnements qui supportent le C (et sont supportés nativement dans de nombreux autres langages modernes), sont familiers à de nombreux programmeurs, et sont généralement bien implémentés, bien documentés et bien supportés. Cependant, comme elles résolvent un problème vaste et difficile, elles comprennent de nombreuses fonctionnalités puissantes et complexes et ont une courbe d’apprentissage difficile en conséquence. En tant que tel, quand une coroutine est tout ce qui est nécessaire, l’utilisation d’un thread peut être exagérée.

Une différence importante entre les threads et les coroutines est que les threads sont généralement programmés de manière préemptive alors que les coroutines ne le sont pas. Parce que les threads peuvent être réordonnancés à tout instant et peuvent s’exécuter simultanément, les programmes utilisant des threads doivent faire attention au verrouillage. En revanche, comme les coroutines ne peuvent être réordonnancées qu’à des moments précis du programme et qu’elles ne s’exécutent pas simultanément, les programmes utilisant des coroutines peuvent souvent éviter complètement le verrouillage. Cette propriété est également citée comme un avantage de la programmation événementielle ou asynchrone.

Puisque les fibres sont ordonnancées de manière coopérative, elles fournissent une base idéale pour la mise en œuvre des coroutines ci-dessus. Cependant, le support système pour les fibres est souvent absent par rapport à celui des threads.

Mise en œuvre pour CEdit

Pour mettre en œuvre des coroutines à usage général, il faut obtenir une deuxième pile d’appel, ce qui est une fonctionnalité non directement supportée par le langage C. Une façon fiable (bien que spécifique à la plateforme) d’y parvenir est d’utiliser une petite quantité d’assemblage en ligne pour manipuler explicitement le pointeur de pile pendant la création initiale de la coroutine. C’est l’approche recommandée par Tom Duff dans une discussion sur ses mérites relatifs par rapport à la méthode utilisée par Protothreads. Sur les plates-formes qui fournissent l’appel système POSIX sigaltstack, une deuxième pile d’appel peut être obtenue en appelant une fonction tremplin à partir d’un gestionnaire de signaux pour atteindre le même objectif en C portable, au prix d’une certaine complexité supplémentaire. Les bibliothèques C conformes à POSIX ou à la spécification Unix unique (SUSv3) fournissaient des routines telles que getcontext, setcontext, makecontext et swapcontext, mais ces fonctions ont été déclarées obsolètes dans POSIX 1.2008.

Une fois qu’une seconde pile d’appels a été obtenue avec l’une des méthodes listées ci-dessus, les fonctions setjmp et longjmp de la bibliothèque C standard peuvent alors être utilisées pour implémenter les commutations entre coroutines. Ces fonctions sauvegardent et restaurent, respectivement, le pointeur de pile, le compteur de programme, les registres sauvegardés par les appels, et tout autre état interne requis par l’ABI, de sorte que le retour à une coroutine après avoir cédé restaure tout l’état qui serait restauré au retour d’un appel de fonction. Les implémentations minimalistes, qui ne s’appuient pas sur les fonctions setjmp et longjmp, peuvent obtenir le même résultat par le biais d’un petit bloc d’assemblage en ligne qui échange simplement le pointeur de pile et le compteur de programme, et bloque tous les autres registres. Cela peut être significativement plus rapide, car setjmp et longjmp doivent stocker de manière conservatrice tous les registres qui peuvent être utilisés selon l’ABI, alors que la méthode clobber permet au compilateur de stocker (en déversant sur la pile) seulement ce qu’il sait être réellement utilisé.

En raison du manque de support direct du langage, de nombreux auteurs ont écrit leurs propres bibliothèques pour les coroutines qui cachent les détails ci-dessus. La bibliothèque libtask de Russ Cox est un bon exemple de ce genre. Elle utilise les fonctions contextuelles si elles sont fournies par la bibliothèque C native ; sinon, elle fournit ses propres implémentations pour ARM, PowerPC, Sparc et x86. D’autres implémentations notables incluent libpcl, coro, lthread, libCoroutine, libconcurrency, libcoro, ribs2, libdill., libaco, et libco.

En plus de l’approche générale ci-dessus, plusieurs tentatives ont été faites pour approcher les coroutines en C avec des combinaisons de sous-routines et de macros. La contribution de Simon Tatham, basée sur le dispositif de Duff, est un exemple notable du genre, et constitue la base de Protothreads et d’implémentations similaires. En plus des objections de Duff, les propres commentaires de Tatham fournissent une évaluation franche des limites de cette approche : « Pour autant que je sache, c’est le pire morceau de hackery C jamais vu dans un code de production sérieux ». Les principaux défauts de cette approximation sont que, en ne maintenant pas un cadre de pile séparé pour chaque coroutine, les variables locales ne sont pas préservées à travers les cessions de la fonction, il n’est pas possible d’avoir plusieurs entrées à la fonction, et le contrôle ne peut être cédé qu’à partir de la routine de niveau supérieur.

Implémentations pour C++Edit

  • C++ coroutines TS (Technical Specification), une norme pour les extensions du langage C++ pour un sous-ensemble sans pile de comportement de type coroutine, est en cours de développement. Visual C++ et Clang supportent déjà des portions majeures dans l’espace de noms std::experimental. coroutines Technical Specification
  • Boost.Coroutine – créée par Oliver Kowalke, est la bibliothèque de coroutine portable officielle publiée de boost depuis la version 1.53. La bibliothèque s’appuie sur Boost.Context et supporte ARM, MIPS, PowerPC, SPARC et X86 sur POSIX, Mac OS X et Windows.
  • Boost.Coroutine2 – également créée par Oliver Kowalke, est une bibliothèque de coroutine portable modernisée depuis boost version 1.59. Elle tire parti des fonctionnalités de C++11, mais supprime le support des coroutines symétriques.
  • Mordor – En 2010, Mozy a mis en open sourcing une bibliothèque C++ implémentant des coroutines, en mettant l’accent sur leur utilisation pour abstraire les E/S asynchrones dans un modèle séquentiel plus familier.
  • CO2 – coroutine stackless basée sur des astuces de préprocesseur C++, fournissant une émulation await/yield.
  • ScummVM – Le projet ScummVM implémente une version légère de coroutines stackless basée sur l’article de Simon Tatham.
  • tonbit::coroutine – C++11 single .h asymmetric coroutine implementation via ucontext / fiber
  • Coroutines a atterri dans Clang en mai 2017, avec une implémentation libc++ en cours.
  • elle par Docker
  • oatpp-coroutines – coroutines sans pile avec un ordonnancement conçu pour les opérations d’E/S à haut niveau de concurence. Utilisé dans l’expérience des 5 millions de connexions WebSocket par Oat++. Fait partie du framework web Oat++.

Implémentations pour C#Edit

  • MindTouch Dream – Le framework REST MindTouch Dream fournit une implémentation de coroutines basée sur le pattern itérateur C# 2.0
  • Caliburn – Le framework de patterns d’écran Caliburn pour WPF utilise des itérateurs C# 2.0 pour faciliter la programmation de l’interface utilisateur, en particulier dans les scénarios asynchrones.
  • Power Threading Library – La bibliothèque Power Threading de Jeffrey Richter met en œuvre un AsyncEnumerator qui fournit un modèle de programmation asynchrone simplifié en utilisant des coroutines basées sur des itérateurs.
  • Le moteur de jeu Unity met en œuvre des coroutines.
  • Servelat Pieces – Le projet Servelat Pieces de Yevhen Bobrov fournit une asynchronie transparente pour les services WCF de Silverlight et la possibilité d’appeler de manière asynchrone toute méthode synchrone. L’implémentation est basée sur l’itérateur Coroutines de Caliburn et les blocs itérateurs de C#.
  • – Le Framework .NET 2.0+ fournit maintenant une fonctionnalité de semi-coroutine (générateur) à travers le pattern itérateur et le mot-clé yield.

C# 5.0 inclut le support de la syntaxe await.

Implémentations pour ClojureEdit

Cloroutine est une bibliothèque tierce fournissant le support des coroutines sans pile dans Clojure. Il est implémenté comme une macro, divisant statiquement un bloc de code arbitraire sur des appels var arbitraires et émettant la coroutine comme une fonction stateful.

Implémentations pour DEdit

D implémente les coroutines comme sa classe de bibliothèque standard Fiber Un générateur rend trivial l’exposition d’une fonction fiber comme une plage d’entrée, rendant toute fiber compatible avec les algorithmes de plage existants.

Implémentations pour JavaEdit

Il existe plusieurs implémentations pour les coroutines en Java. Malgré les contraintes imposées par les abstractions de Java, la JVM n’exclut pas cette possibilité. Il y a quatre méthodes générales utilisées, mais deux brisent la portabilité du bytecode parmi les JVM conformes aux normes.

  • JVM modifiées. Il est possible de construire une JVM patchée pour supporter les coroutines de manière plus native. La JVM Da Vinci a fait l’objet de la création de correctifs.
  • Modification du bytecode. La fonctionnalité des coroutines est possible en réécrivant le bytecode Java régulier, soit à la volée, soit au moment de la compilation. Les boîtes à outils comprennent Javaflow, Java Coroutines, et Coroutines.
  • Mécanismes JNI spécifiques à la plateforme. Ceux-ci utilisent des méthodes JNI implémentées dans les bibliothèques OS ou C pour fournir la fonctionnalité à la JVM.
  • Les abstractions de threads. Les bibliothèques de coroutine qui sont mises en œuvre en utilisant des threads peuvent être lourdes, bien que les performances varient en fonction de la mise en œuvre des threads de la JVM.

Implémentations en JavaScriptEdit

  • node-fibers
    • Fibjs – fibjs est un moteur d’exécution JavaScript construit sur le moteur JavaScript V8 de Chrome. fibjs utilise les fibres-switch, le style sync et le modèle d’E/S non bloquant pour construire des systèmes évolutifs.
  • Depuis ECMAScript 2015, une fonctionnalité de coroutine sans pile par le biais de « générateurs » et d’expressions de rendement est fournie.

Implémentations pour KotlinEdit

Kotlin implémente des coroutines dans le cadre d’une bibliothèque de première partie.

Implémentations pour Modula-2Edit

Modula-2 tel que défini par Wirth implémente des coroutines comme partie de la bibliothèque standard SYSTEM.

La procédure NEWPROCESS() remplit un contexte étant donné un bloc de code et un espace pour une pile comme paramètres, et la procédure TRANSFER() transfère le contrôle à une coroutine étant donné le contexte de la coroutine comme paramètre.

Implémentation dans MonoEdit

Le Common Language Runtime de Mono a un support pour les continuations, à partir desquelles les coroutines peuvent être construites.

Implémentation dans le .NET Framework en tant que fibresEdit

Pendant le développement du .NET Framework 2.0, Microsoft a étendu la conception des API d’hébergement du Common Language Runtime (CLR) pour gérer l’ordonnancement basé sur les fibres avec un œil vers son utilisation en mode fibre pour le serveur SQL. Avant la publication, le support du crochet de commutation de tâches ICLRTask::SwitchOut a été supprimé en raison de contraintes de temps.Par conséquent, l’utilisation de l’API de fibre pour commuter les tâches n’est actuellement pas une option viable dans le .NET Framework.

Implémentations pour PerlEdit

  • Coro

Les coroutines sont nativement implémentées dans tous les backends Raku.

Implémentations pour PHPEdit

  • Amphp
  • Coroutine implémentée d’une manière qui ressemble aux fonctions Python, et un peu de Go, de nombreux exemples montrant là code converti avec le même nombre de lignes et le même comportement.

Implémentations pour PythonEdit

  • Python 2.5 implémente un meilleur support pour les fonctionnalités de type coroutine, basé sur les générateurs étendus (PEP 342)
  • Python 3.3 améliore cette capacité, en supportant la délégation à un sous-générateur (PEP 380)
  • Python 3.4 introduit un cadre complet d’E/S asynchrones tel que normalisé dans le PEP 3156, qui comprend des coroutines qui tirent parti de la délégation à un sous-générateur
  • Python 3.5 introduit un support explicite des coroutines avec la syntaxe async/await (PEP 0492).
  • Depuis Python 3.7, async/await sont devenus des mots-clés réservés .
  • Eventlet
  • Greenlet
  • gevent
  • stackless python
  • Abandonné
    • Shrapnel (dernière version 2015)
    • Kamaelia (dernière version 2010)
    • cogen (dernière version 2009)
    • multitask (dernière version 2007)
    • chiral

Implémentations pour RubyEdit

  • Ruby 1.9 supporte nativement les coroutines qui sont implémentées comme des fibres, qui sont des semi-coroutines.
  • Une implémentation par Marc De Scheemaecker
  • Ruby 2.5 et plus supporte nativement les coroutines qui sont implémentées comme des fibres
  • Une implémentation par Thomas W Branson

Implémentations pour RustEdit

Rust supporte les coroutines depuis la version 1.39 .Il existe également un runtime asynchrone alternatif (projet plus ancien que le runtime standard de rust) : tokio

Mise en œuvre pour ScalaEdit

Scala Coroutines est une mise en œuvre de coroutines pour Scala. Cette implémentation est une extension au niveau de la bibliothèque qui s’appuie sur le système de macros Scala pour transformer statiquement des sections du programme en objets coroutines. En tant que telle, cette implémentation ne nécessite pas de modifications dans la JVM, elle est donc entièrement portable entre différentes JVM et fonctionne avec des backends Scala alternatifs, tels que Scala.js, qui compile en JavaScript.

Scala Coroutines s’appuie sur la macro coroutine qui transforme un bloc de code normal en une définition de coroutine. Une telle définition de coroutine peut être invoquée avec l’opération call, qui instancie un cadre de coroutine. Une trame de coroutine peut être reprise avec la méthode resume, qui reprend l’exécution du corps de la coroutine, jusqu’à atteindre un mot-clé yieldval, qui suspend la trame de la coroutine. Les coroutines Scala exposent également une méthode snapshot, qui duplique effectivement la coroutine. Une description détaillée des coroutines Scala avec des instantanés est apparue à ECOOP 2018, ainsi que leur modèle formel.

Implémentations pour SchemeEdit

Puisque Scheme fournit un support complet pour les continuations, l’implémentation de coroutines est presque triviale, nécessitant seulement qu’une file d’attente de continuations soit maintenue.

Implémentations pour SmalltalkEdit

Puisque, dans la plupart des environnements Smalltalk, la pile d’exécution est un citoyen de première classe, les coroutines peuvent être implémentées sans bibliothèque supplémentaire ou support VM.

Implémentations pour SwiftEdit

  • SwiftCoroutine – Bibliothèque de coroutines Swift pour iOS, macOS et Linux.

Implémentation pour Tool Command Language (Tcl)Edit

Depuis la version 8.6, le Tool Command Language supporte les coroutines dans le langage de base.

Mise en œuvre pour ValaEdit

Vala met en œuvre un support natif pour les coroutines. Ils sont conçus pour être utilisés avec une boucle principale Gtk, mais peuvent être utilisés seuls si l’on prend soin de s’assurer que le callback de fin n’aura jamais à être appelé avant de faire, au moins, un yield.

Implémentations dans les langages d’assemblageEdit

Les langages d’assemblage dépendant de la machine fournissent souvent des méthodes directes pour l’exécution de coroutines. Par exemple, dans MACRO-11, le langage d’assemblage de la famille des mini-ordinateurs PDP-11, le passage « classique » en coroutine est effectué par l’instruction « JSR PC,@(SP)+ », qui saute à l’adresse sortie de la pile et pousse l’adresse de l’instruction courante (c’est-à-dire celle de la suivante) sur la pile. Sur VAXen (en Macro-32), l’instruction comparable est « JSB @(SP)+ ». Même sur un Motorola 6809, il existe l’instruction « JSR  » ; notez le « ++ », car 2 octets (d’adresse) sont extraits de la pile. Cette instruction est très utilisée dans le (standard) ‘monitor’ Assist 09.

Laisser un commentaire