Está en la página 1de 5

Multitarea en Lua

Voy a compartir con vosotros, una nueva característica de Lua que he estado estudiando, a
pesar de ser un lenguaje interpretado permite multitarea. No es una multitarea "real", o
preferente tal como la de POSIX que podemos encontrar en los lenguajes compilados, es
una tarea "cooperativa". Esto quiere decir, que es el programador el que tiene que crear
los puntos de interrupción en su código para dar paso a otras tareas, en vez de gestionar
dichas interrupciones el Sistema Operativo o el lenguaje (como por ejemplo hace POSIX).

Ventajas de la multitarea cooperativa de Lua:


     Funciona en Lua nativo. Es decir, no necesitas usar ninguna aplicación externa en
un lenguaje compilado.
     Será compatible en cualquier intérprete Lua que uses.
     No hace falta programar nada en otros lenguajes.

Desventajas:
     No es una "multitarea real", en sentido amplio de la palabra.
     El Programador tiene que gestionar los puntos de interrupción, lo que requiere
una buena planificación del código y tener muy claro el tiempo que va tardar en
completarse una rutina o algoritmo determinado.
     Se puede colapsar toda la aplicación si un proceso no termina

Mejor voy a explicar todo esto con un ejemplo, veréis que no es muy complicado. En
primer lugar vamos a hacer un código normal, tiene 3 funciones con un código básico a las
que hemos llamado proceso1, 2 y 3.

function proceso1()
local n
print("Proceso1: Imprime los números de 1 al 4 y luego del 4 al 1")
for n = 1, 4 do
print("proceso1: ", n)
end
--coroutine.yield()
for n = 4, 1, -1 do
print("proceso1: ", n)
end
 
end
 
function proceso2()
local j, n
print("Proceso2: Imprime 2 veces los números de 1 al 3")
for j = 1, 2 do
for n = 1, 3 do
print("proceso2 (ronda "..j.."): ",n)
end
--coroutine.yield()
end
end
 
function proceso3(num)
local n, total
print("Proceso3: Recibe un número y le suma 5 en 3 veces, lo devuelve
en cada suma")
total = num
for n = 1, 3 do
total = total + 5
print("proceso3 (ronda "..n..") recibido "..num.." y acumulado:",
total)
end
end
 
proceso1()
proceso2()
proceso3(50)

La forma de ejecutar este código, lógicamente es llamando a una función o proceso cada
vez. Si lo ejecutamos veríamos por la consola lo siguiente:

Proceso1: Imprime los números de 1 al 4 y luego del 4 al 1


proceso1: 1
proceso1: 2
proceso1: 3
proceso1: 4
proceso1: 4
proceso1: 3
proceso1: 2
proceso1: 1
Proceso2: Imprime 2 veces los números de 1 al 3
proceso2 (ronda 1): 1
proceso2 (ronda 1): 2
proceso2 (ronda 1): 3
proceso2 (ronda 2): 1
proceso2 (ronda 2): 2
proceso2 (ronda 2): 3
Proceso3: Recibe un número y le suma 5 en 3 veces, lo devuelve en cada
suma
proceso3 (ronda 1) recibido 50 y acumulado: 55
proceso3 (ronda 2) recibido 50 y acumulado: 60
proceso3 (ronda 3) recibido 50 y acumulado: 65

Fijaos bien en el código anterior, hasta que lo comprendáis, ES MUY IMPORTANTE


para entender lo que viene después. Para facilitar las indicaciones, he numerado las
lineas del siguiente listado.

1 function proceso1()
2 local n
3 print("Proceso1: Imprime los números de 1 al 4 y luego del 4
al 1")
4 for n = 1, 4 do
5 print("proceso1: ", n)
6 end
7 coroutine.yield()
8 for n = 4, 1, -1 do
9 print("proceso1: ", n)
10 end
11 end
12
13 function proceso2()
14 local j, n
15 print("Proceso2: Imprime 2 veces los números de 1 al 3")
16 for j = 1, 2 do
17 for n = 1, 3 do
18 print("proceso2 (ronda "..j.."): ",n)
19 end
20 coroutine.yield()
21 end
22 end
23
24 function proceso3(num)
25 local n, total
26 print("Proceso3: Recibe un número y le suma 5 en 3 veces, lo
devuelve en cada suma")
27 total = num
28 for n = 1, 3 do
29 total = total + 5
30 print("proceso3 (ronda "..n..") recibido "..num.." y
acumulado:", total)
31 coroutine.yield(total)
32 end
33 end
34
35 --Creamos las corutinas o "procesos"
36 coPro1 = coroutine.create(proceso1)
37 coPro2 = coroutine.create(proceso2)
38 coPro3 = coroutine.create(proceso3)
39 local valorPro3, foo
40 while true do
41 --Comprobamos que todas las corutinas hayan terminado
42 --status() devuelve "dead" cuando el proceso o función haya
terminado
43 if coroutine.status(coPro1) == "dead" and
44 coroutine.status(coPro2) == "dead" and
45 coroutine.status(coPro3) == "dead" then
46 break
47 end
48 --Vamos ejecutando "las partes" de los procesos
49 coroutine.resume(coPro1)
50 coroutine.resume(coPro2)
51 foo, valorPro3 = coroutine.resume(coPro3, 50)
52 print("El valor del proceso3, ahora vale: ", valorPro3)
53 end

Lo primero que vemos es que en los procesos (en realidad son funciones, pero quiero
llamarlo procesos para que comprendáis mejor el concepto de multitarea), en las lineas 7,20
y 31, hemos incluido una instrucción coroutine.yield() (ceder). Esto representa el punto
donde queremos que nuestro proceso termine temporalmente y pase el control al proceso
principal. En este ejemplo hay un yield() por proceso, pero se pueden poner los que
quieran, eso sí, como veréis más adelante, es conveniente planificar bien dónde se colocan.

En las lineas 36, 37 y 38. Creamos las "corutinas", coPro1, 2, y 3. Contienen un puntero de
un proceso. El parámetro es la función llamada. Por ejemplo en la linea 36, estamos
diciendo que coPro1, es un proceso que apunta a la función proceso1.

En la linea 39, definimos 2 variables locales, que más adelante os diré para qué son.
NOTA: Acostrumbraros a definir las variables como locales si no os pueden dar conflicto
con otras en otros módulos con el mismo nombre, error MUY difícil de detectar.

En 40, creamos un bucle, este bucle funcionará hasta que todos los procesos terminen. La
forma de hacer esto es comprobar el estado (status) de cada proceso como se hace en la
linea 43. status devuelve "dead" si el proceso ha terminado (esta muerto vamos xDD).

Bien, ahora en 49, 50 y 51, viene quizás lo más importante, hacemos un resume (resumen)
de los procesos. La primera vez que llamamos a resume(), se ejecuta el proceso hasta que
encuentra una instrucción yield(), momento en el que regresa. Al volver a llamar a
resume(), continúa donde lo dejó, y sigue hasta el yield(), así hasta que acaba el proceso.

En 51, he incluido estas variables para que veáis que se pueden enviar variables, y recibir
resultados de los procesos. Fijaos que hemos puesto un segundo parámetro en resume, el
valor 50, que es el parámetro que le enviamos al proceso3 inicialmente. Lua, puede
devolver más de un valor, al contrario de lo que suele ser "normal" en otros lenguaje que
devuelven uno o ninguno. yield() SIEMPRE devuelve true, y los parámetros que se le
digan separados por comas, en este caso en la linea 31 yield(), sólo devuelve uno más, el
total. Por cierto, no me preguntéis porque siempre devuelve true como primer parámetro y
no le busquéis sentido porque no lo tiene, xDDDDD. El hecho de usar "foo", es poner una
variable para que reciba el valor true que no nos interesa procesar.

Esta es la salida que produce el código usando corutinas:

Proceso1: Imprime los números de 1 al 4 y luego del 4


proceso1: 1
proceso1: 2
proceso1: 3
proceso1: 4
Proceso2: Imprime 2 veces los números de 1 al 3
proceso2 (ronda 1): 1
proceso2 (ronda 1): 2
proceso2 (ronda 1): 3
Proceso3: Recibe un número y le suma 5 en 3 veces, lo
proceso3 (ronda 1) recibido 50 y acumulado: 55
El valor del proceso3, ahora vale: 55
proceso1: 4
proceso1: 3
proceso1: 2
proceso1: 1
proceso2 (ronda 2): 1
proceso2 (ronda 2): 2
proceso2 (ronda 2): 3
proceso3 (ronda 2) recibido 50 y acumulado: 60
El valor del proceso3, ahora vale: 60
proceso3 (ronda 3) recibido 50 y acumulado: 65
El valor del proceso3, ahora vale: 65

Se puede ver cómo se han ido alternando los diferente procesos, lo cual da una sensación de
"multitarea".

Terminado:
Aunque esto sea una "multitarea cooperativa", en vez de una real o preferente, hace un
apaño y más teniendo en cuenta de que se trata de un lenguaje interpretado. Espero que os
haya servido de algo.

Los comentarios son de agradecer y motivan a crear más tutoriales ;-).

Un saludo.

También podría gustarte