Lua: Le tutoriel  wxWidgets
Lua
Les coroutines: Pipes and Filters.

Un des exemples d'utilisation des coroutines, est celui de production --> consommation!...

Explication.
Supposez une fonction qui produit continuellement les valeurs (par exemple, la lecture d'un fichier) et une autre fonction qui consomme en permanence ces valeurs (par exemple, en écrivant les valeurs récupérées dans un autre fichier).
Typiquement, ces deux fonctions pourraient ressembler à celles-ci:

		function producteur()
		  while true do
			local x = io.read() -- production d'une nouvelle valeur.
			envoyer(x)          -- envoie à consommateur.
		  end
		end
		
		function consommateur()
		  while true do
			local x = recevoir() -- reçoit de producteur.
			io.write(x, "\n")    -- utilise la nouvelle valeur.
		  end
		end
				

Dans cette façon de faire, le producteur et le consommateur tournent continuellement, mais il reste facile de les arrêter lorsqu'il n'y aura plus de données.

Le problème ici est: " comment faire correspondre envoyer et recevoir? "
C'est un cas typique du problème de " qui a la main sur la boucle... "

Tant que le producteur et le consommateur sont actifs, les deux ont leurs propres boucles principales, et les deux supposent que l'autre est un service appelable.

Dans cet exemple particulier, il serait facile de modifier la structure de l'une des fonctions, en déroulant sa boucle pour en faire un agent passif.
Toutefois, ce changement de structure pourrait être plus difficile dans d'autres scénarios réels.

Les coroutines fournissent un outil idéal pour faire correspondre producteur et consommateur, car une paire de resume-yield tourne à l'envers la relation typique entre l'appelant et l'appelé.

Quand une coroutine appelle yield , elle n'entre pas dans une nouvelle fonction, mais retourne plutôt un appel en attente (pour resume).

De même, un appel à resume ne démarre pas une nouvelle fonction, mais retourne un appel à yield.

Cette propriété est exactement celle dont vous avez besoin pour faire correspondre envoyer à recevoir, de telle sorte que chacun agisse comme s'il était le maître et l'autre l'esclave...

Donc, recevoir des resume de producteur afin qu'il puisse produire une nouvelle valeur, et envoyer des yield de la nouvelle valeur pour le consommateur.

		function recevoir()
			local status, value = coroutine.resume(producteur)
			return value
		end
		function envoyer(x)
			coroutine.yield(x)
		end
				
Bien sûr, le producteur doit désormais être une coroutine:
		producteur = coroutine.create(
		function()
			while true do
				local x = io.read()  -- produit une nouvelle valeur
				envoyer(x)
			end
		end)
				

Dans cette conception, le programme appelle en premier la fonction recevoir()

Lorsque celle-ci a besoin d'un élément, elle fait appel à coroutine.resume(), qui boucle jusqu'à ce qu'elle ait quelque chose à envoyer(x), puis s'arrête jusqu'au redémarrage de recevoir()
Il s'agit là d'une conception axée sur la réception...

Vous pouvez étendre cette conception de "filtres", qui ne sont en fait que des tâches qui se placent entre le producteur et le consommateur afin d'opérer une sorte de transformation dans les données.

Un filtre va chercher (resume) sur un producteur pour obtenir de nouvelles valeurs et retourne (yield) des valeurs transformées à un consommateur.
(Un filtre resume sur un producteur pour obtenir de nouvelles valeurs et yield des valeurs transformées à un consommateur.)

Vous pouvez aussi, ajouter au code précédent, un filtre qui insère un numéro de ligne au début de chaque ligne.
Le code complet, pourrait alors ressembler à ceci:

		function receive(prod)
			local status, value = coroutine.resume(prod)
			return value
		end

		function send(x)
			coroutine.yield(x)
		end
	 
		function producer()
		  return coroutine.create(function()
			while true do
				local x = io.read()  -- produit une nouvelle valeur
				send(x)
			end
		  end)
		end
		 
		function filter(prod)
		  return coroutine.create(function()
			local line = 1
			while true do
			  local x = receive(prod) -- obtient une nouvelle valeur
			  x = string.format("%5d %s", line, x)
			  send(x)                 -- envoie au consommateur
			  line = line + 1
			end
		  end)
		end
	  
		function consumer (prod)
		  while true do
			local x = receive(prod) -- obtient une nouvelle valeur
			io.write(x, "\n")       -- consomme une nouvelle valeur
		  end
		end
				

Le dernier bit crée simplement les composants dont il a besoin, les relie, et commence le consommateur final:

Les coroutines sont une sorte de multithreading non-préemptif*.
Alors que dans les pipes, chaque tâche s'exécute dans un processus séparé, les coroutines elles, exécutent chaque tâche dans une coroutine séparée.

Les pipes fournissent une zone tampon entre l'écrivain (producteur) et le lecteur (la consommation) ce qui laisse une certaine liberté dans leurs vitesses relatives.

Ceci est important dans le contexte de pipes, car le coût de la commutation entre les processus est élevé.

Avec les coroutines, le coût de la commutation entre les différentes tâches est beaucoup plus petit... environ le même que lors d'un appel à une fonction.



*Préemption:
En informatique, la préemption est la capacité d'un système d'exploitation multitâche à exécuter ou stopper une tâche planifiée en cours.
Et donc à contrario, un système d'exploitation non-préemptif, ou collaboratif, est un système dans lequel c'est le processus en cours d'exécution qui prend la main et est seul juge du moment où il la rend.
logo wxWidgets Le savoir ne vaut que s'il est partagé par tous...
logo-internet_32x32.png Dernière mise à jour, le 17 décembre 2012.
Valid XHTML 1.0 Transitional

wxlualogo
Flèche haut
Flèche gauche
Flèche haut
Flèche droite