Bienvenidos sean a este post, en el caso comenzaremos a hablar sobre corutinas, pero que es corutina? La corutina es similar a un proceso (thread) es decir una linea de ejecucion con su propio pila (stack), sus propias variables locales y su propio puntero de instruccion pero compartiendo las variables locales y mayormente cualquier otra cosa mas con cualquier otra corutina, la principal diferencia entre la corutina y un proceso es que un programa con procesos corre procesos de forma concurrente, y las corutinas por el otro lado son colaborativas es decir que en cualquier momento un programa con corutinas esta corriendo una sola de ellas y las que estan corriendo suspenden su ejecucion solo cuando se le solicita explicitamente que se suspendan.

Anuncios

Lua empaca todas las funciones relacionadas a las corutinas en la tabla de corutina, la funcion encargada de crear las corutinas es create, tiene un simple argumento, una funcion con el codigo que la corutina correra, devuelve un valor de tipo thread lo cual representa una nueva corutina, y a menudo el argumento para crearlo es una funcion anonima:

co = coroutine.create(function () print("hola") end)

Su salida es:
> print(co)
thread: 0x13ee364

Una corutina puede tener uno de los siguientes cuatro estados:

  • suspended
  • running
  • dead
  • normal

En el momento de creacion de una corutina, esta comienza con una estado suspended, lo cual significa que una corutina no ejecuta su bloque automaticamente cuando lo creamos, para chequearlo podemos hacerlo de la siguiente forma:

> print(coroutine.status(co))
suspended
>

La funcion coroutine.resume reinicia la ejecucion de una corutina cambiando el estado de suspended a running:

> print(coroutine.resume(co))
hola
true
>

Como pueden ver al pasarlo a estado running mostro el contenido de la funcion (hola) y termina, dejandolo en estado dead del cual no regresa:

> print(coroutine.status(co))
dead
>
Anuncios

Ok, hasta ahora hemos visto que es una forma mas complicada de llamar a una funcion, pero su verdadero poder proviene de la funcion yield, la cual permite a una corutina en estado running suspender su propia ejecucion para ser reanudada mas tarde, veamos un ejemplo simple:

co = coroutine.create(function()
	for i = 1,10 do
		print("co",i)
		coroutine.yield()
	end
end)

Veamos el estado actual de esta corutina:

> print(coroutine.status(co))
suspended
>

Si lo restauramos, este avanzara hasta el proximo yield():

> print(coroutine.resume(co))
co      1
true
>

Como pueden ver este imprimira un numero y volvera a suspended:

> print(coroutine.status(co))
suspended
>

Lo bueno es que no paso a dead sino que sigue esperando para volver a ejecutarse, si volvieramos a llamarlo por medio de resume las veces que se necesita llegaremos al ultimo valor que es 10 y obtendremos este estado:

> print(coroutine.resume(co))
co      10
true
> print(coroutine.resume(co))
true
> print(coroutine.status(co))
dead
>
Anuncios

Como pueden ver una vez pasado el ultimo numero no nos devolvio nada y por esto cambio su estado a dead, si volvemos a usar resume obtendremos la siguiente respuesta:

> print(coroutine.resume(co))
false   cannot resume dead coroutine
>

Como se termino el bucle, salimos de la funcion porque ya retorno todos los valores, como esta en estado dead al reanudarlo nos devuelve un estado false a diferencia de antes que era true y una notificacion de error, como Lua corre el resume en modo protegido, es decir que si hay un error dentro de la corutina no veremos un mensaje de error sino en su lugar regresara a la llamada de resume, cuando una corutina reanuda otra, despues de todo no esta suspendida y no podemos resumirla, sin embargo tampoco esta corriendo porque la corutina este corriendo es la otra, por lo tanto su propio estado es lo que llamamos estado normal, una facilidad util en Lua es el par resume-yield que puede intercambiar datos, el primer resume que no tiene su correspondiente yield esperando por ello, pasando sus argumentos extras como argumentos para la funcion de la corutina principal, veamos el siguiente caso:

> co = coroutine.create(function (a,b,c)
>> print("co", a,b,c)
>> end)
> coroutine.resume(co, 1,2,3)
co      1       2       3
true
>

Como pueden ver recibe tres argumentos (a,b,c) la funcion imprime el literal co y los valores de los tres argumentos recibidos, si usamos a resume primero informaremos la corutina (co) y luego le enviaremos tres valores para ser asignados a la funcion, observen como nos devolvio todos los elementos informados y el valor true, el cual nos simboliza que no hubo ningun error, pasemos a ver que pasa al enviar un argumento al correspondiente yield:

Anuncios
> co = coroutine.create(function (a,b)
>> coroutine.yield(a + b, a - b)
>> end)
> coroutine.resume(co, 20, 10)
true    30      10
>

En este caso la corutina recibe dos argumentos, despues usaremos a yield para efectuar dos operaciones, la primera sumara los argumentos recibidos y la segunda restara los argumentos recibidos, cuando usamos resume notificamos la corutina y dos valores como argumentos, observen como nos devolvio el true y efectuo las operaciones devolviendo los valores porque yield devolvera siempre los argumentos pasados a su correspondiente resume, pasemos a ver el siguiente ejemplo:

co = coroutine.create(function ()
	print("co", coroutine.yield())
end)

Si lo ejecutamos obtendremos las siguientes salidas:

> coroutine.resume(co)
true
> coroutine.resume(co,4,5)
co      4       5
true
>

En el primer caso solo nos notifico que se resumio pero al no informar ningun valor para el yield la corutina sigue suspendida, en cambio en la segunda llamada al resume si informamos valores se usara el yield y este no solamente nos lo mostrara sino que a su vez cumplio todas las condiciones para pasar al estado dead, por ultimo cuando una corutina termina cualquiera de los valores regresados por la funcion principal va al resume correspondiente:

co = coroutine.create(function ()
	return 6, 7
end)

Si lo llamamos obtendremos la siguiente salida:

> print(coroutine.resume(co))
true    6       7
>

Aunque rara vez usemos todas estas facilidades en una corutina pero todas tienen sus usos, para aquellos que conocen algo de corutinas debemos clarificar algunas temas antes de continuar, Lua ofrece algo llamado corutinas asimetricas, lo cual significa que tiene una funcion para suspender la ejecucion de una corutina y otra funcion para reanudar una corutina suspendida, algunos otros lenguajes ofrecen corutinas simetricas donde solo hay una funcion para transferir control de cualquier corutina a la otra, algunas personas llaman a las corutinas asimetricas como semi-corutinas (sin ser simetricas no son realmente co), sin embargo otras personas usan el termino semi-corutina para denotar una implementacion restringida de la corutina, donde una corutina puede suspender su ejecucion solo cuando no esta llamando a ninguna funcion, lo cual implica que no tenga ninguna llamada pendiente en su pila de control.

Anuncios

En otras palabras, suele el bloque principal de la corutina usar yield, un generador en Python es un ejemplo del significado de semi-corutinas, las diferencias entre corutinas y generadores (como se presentan en Python) son un poco mas profundas que las diferencias entre las corutinas simetricas y asimetricas.

Los generadores no son lo suficientemente poderosos para implementar muchas construcciones interesantes que podamos escribir con corutinas completas, Lua ofrece corutinas asimetricas completas por lo que aquellos que prefieran corutinas asimetricas pueden implementarlas en la parte superior de las facilidades asimetricas de LUA.

Anuncios

En resumen, hoy hemos visto que son las corutinas, que es una corutina basica, sus estados, su sintaxis, sus funciones mas basicas para pasar de un estado a otro, como son las corutinas simetricas, como son las asimetricas, y algunos conceptos basicos mas, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00