Corroutines à Lua

Ongle coroutine Elle s'apparente à un thread, c'est une ligne d'exécution avec sa propre pile, ses propres variables locales et son propre pointeur pour les instructions, mais avec la particularité qu'elle partage des variables globales et tout autre élément avec les autres coroutines.

Mais nous devons préciser qu'il existe des différences entre les fils et les coroutines, la principale différence est qu'un programme qui utilise des threads les exécute simultanément, le coroutines d'autre part, elles sont collaboratives, où un programme qui utilise des coroutines n'en exécute qu'une, et la suspension de celles-ci n'est obtenue que si elle est explicitement demandée.

Le coroutines Ils sont extrêmement puissants, voyons ce que recouvre ce concept et comment nous pouvons les utiliser dans nos programmes.

Des concepts basiques


Toutes les fonctions liées aux coroutines dans Lua se trouvent dans la table de coroutine, où la fonction créer () nous permet de les créer, il a un argument simple et est la fonction avec le code que la coroutine exécutera, où son retour est une valeur du type thread, qui représente la nouvelle coroutine. Même l'argument pour créer la coroutine est parfois une fonction anonyme comme dans l'exemple suivant :
 co = coroutine.create (function () print ("Bonjour Solvetic") end)
Ongle coroutine il peut avoir quatre états différents :
  • suspendu
  • pressé
  • morte
  • Ordinaire

Lorsque nous le créons, il démarre dans l'état abandonné, ce qui signifie que la coroutine ne s'exécute pas automatiquement lorsqu'elle est créée pour la première fois. Le statut d'une coroutine peut être consulté de la manière suivante :

 imprimer (coroutine.status (co))
Où pour pouvoir exécuter notre coroutine, nous n'avons qu'à utiliser la fonction de résume(), ce qu'il fait en interne est de changer son statut de suspendu à en cours d'exécution.
 coroutine.resume (co)
Si nous rassemblons tout notre code et ajoutons une ligne supplémentaire pour interroger le statut supplémentaire de notre coroutine après avoir fait résume on peut voir tous les états par lesquels il passe :
 co = coroutine.create (function () print ("Bonjour Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))
Nous allons dans notre terminal et exécutons notre exemple, voyons le résultat de notre programme :
 lua coroutines1.lua thread: 0x210d880 Suspendu Bonjour Solvetic mort
Comme nous pouvons le voir la première impression de la coroutine est la valeur du fil, alors nous avons l'état suspendu, et c'est très bien puisque c'est le premier état lors de la création, puis avec résume Nous exécutons la coroutine avec laquelle il imprime le message et après cela son statut est morteau fur et à mesure qu'il remplissait sa mission.

À première vue, les coroutines peuvent sembler une manière compliquée d'appeler des fonctions, mais elles sont beaucoup plus complexes que cela. La puissance du même repose en grande partie sur la fonction rendement () qui permet de suspendre une coroutine en cours d'exécution afin de reprendre son fonctionnement plus tard, voyons un exemple d'utilisation de cette fonction :

 co = coroutine.create (function () for i = 1.10 do print ("résumant coroutine", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co ) coroutine .resume (co)
Cette fonction s'exécutera jusqu'au premier rendement, et que nous ayons ou non un cycle pour, il ne s'imprimera qu'en fonction de tant de résume Ayons pour notre coroutine, pour finir voyons la sortie via le terminal :
 lua coroutines 1. lua 1 2 3 4
Ce serait la sortie par le terminal.

Filtres


L'un des exemples les plus clairs qui expliquent les coroutines est le cas de consommateur Oui Générateur d'information. Supposons alors que nous ayons une fonction qui génère en permanence des valeurs à partir de la lecture d'un fichier, puis que nous ayons une autre fonction qui les lit, voyons un exemple illustratif de ce à quoi ces fonctions pourraient ressembler :
 générateur de fonction () tant que vrai faire local x = io.read () envoyer (x) fin fin consommateur de fonction () tant que vrai faire local x = recevoir () io.écrire (x, "\ n") fin fin
Dans cet exemple, le consommateur et le générateur fonctionnent sans aucun repos et nous pouvons les arrêter lorsqu'il n'y a plus d'informations à traiter, mais le problème ici est de savoir comment synchroniser les fonctions de Envoyer() Oui recevoir(), puisque chacun d'eux a sa propre boucle et que l'autre est supposé être un service appelable.

Mais avec les coroutines, ce problème peut être résolu rapidement et facilement, en utilisant la double fonction reprendre / céder nous pouvons faire fonctionner nos fonctions sans problème. Quand une coroutine appelle la fonction rendement, il n'entre pas dans une nouvelle fonction mais renvoie un appel en attente et qui ne peut quitter cet état qu'en utilisant resume.

De même en appelant résume ne démarre pas non plus une nouvelle fonction, il renvoie un appel d'attente à rendement, résumant ce processus est celui dont nous avons besoin pour synchroniser les fonctions de Envoyer() Oui recevoir(). En appliquant cette opération, nous aurions à utiliser recevoir() Appliquer résume au générateur pour générer les nouvelles informations, puis Envoyer() appliquer rendement Pour le consommateur, voyons à quoi ressemblent nos fonctions avec les nouveaux changements :

 fonction recevoir () état local, valeur = coroutine.resume (générateur) valeur de retour fin fonction envoyer (x) coroutine.yield (x) fin gen = coroutine.create (fonction () alors que vrai do local x = io.read () envoyer (x) fin fin)
Mais nous pouvons encore améliorer notre programme, et c'est en utilisant le filtres, qui sont des tâches qui fonctionnent à la fois comme des générateurs et des consommateurs, ce qui constitue un processus de transformation de l'information très intéressant.

UNE filtre peut faire résume d'un générateur pour obtenir de nouvelles valeurs puis appliquer rendement transformer les données pour le consommateur. Voyons comment nous pouvons facilement ajouter des filtres à notre exemple précédent :

 gène = générateur () fil = filtre (gène) consommateur (fil)
Comme on peut le voir, c'était extrêmement simple, où en plus d'optimiser notre programme nous avons gagné des points en lisibilité, importants pour la maintenance future.

Corroutines comme itérateurs


L'un des exemples les plus clairs du générateur/consommateur est le itérateurs présent dans les cycles récursifs, où un itérateur génère des informations qui seront consommées par le corps dans le cycle récursif, il ne serait donc pas déraisonnable d'utiliser des coroutines pour écrire ces itérateurs, même les coroutines ont un outil spécial pour cette tâche.

Pour illustrer l'usage que l'on peut en faire coroutines, nous allons écrire un itérateur pour générer les permutations d'un tableau donné, c'est-à-dire placer chaque élément d'un tableau dans la dernière position et le retourner, puis générer récursivement toutes les permutations des éléments restants, voyons comment notre la fonction d'origine serait sans inclure les coroutines :

 fonction print_result (var) for i = 1, #var do io.write (var [i], "") end io.write ("\ n") end
Maintenant, ce que nous faisons, c'est changer complètement ce processus, d'abord nous changeons le print_result () par rendement, voyons le changement :
 fonction permgen (var1, var2) var2 = var2 ou # var1 if var2 <= 1 then coroutine.yield (var1) else
Ceci est un exemple illustratif pour montrer comment fonctionnent les itérateurs, cependant Lua nous fournit une fonction appelée envelopper qui ressemble à créerCependant, il ne renvoie pas de coroutine, il renvoie une fonction qui, lorsqu'elle est appelée, résume une coroutine. Puis à utiliser envelopper nous ne devons utiliser que les éléments suivants :
 fonction permutations (var) return coroutine.wrap (function () permgen (var) end) end
Habituellement, cette fonction est beaucoup plus facile à utiliser que créer, puisqu'elle nous donne exactement ce dont nous avons besoin, c'est-à-dire la résumer, cependant elle est moins souple puisqu'elle ne permet pas de vérifier l'état de la coroutine créée avec envelopper.

Les coroutines en Lua Ils sont un outil extrêmement puissant pour traiter tout ce qui concerne les processus qui doivent être exécutés main dans la main mais en attendant l'achèvement de celui qui fournit l'information, on pourrait aussi voir leur utilisation pour résoudre des problèmes complexes en ce qui concerne les processus générateur/consommateur et aussi optimiser la construction des itérateurs dans nos programmes.

wave wave wave wave wave