Anuncios Google

[TUTORIAL] 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éprete 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
    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
    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 incluído 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.


LuaDiE: Crea en Lua sin teclear código. Compatible HM7, HMv2, LuaPlayer, LuaDEV y PGE.


Anuncios Google

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de moikop

Muy buen tuto.

Muy bien explicado. Siempre me despertó un poco de curiosidad la "multitarea" de Lua. Y ahora ya esta todo claro XD.

Un saludo, te lo paso a portada ;-)


Para recibir ayuda por parte de otros usuarios más rápidamente, recomendamos que pongas títulos descriptivos y no utilices abreviaturas (estilo MSN) en tus post de los foros. Recuerda que accediendo al Manual del perfecto forero y las Normas de la Comunidad aprenderas trucos para resolver tus dudas antes.

No preguntes por MP, mejor pregunta aquí.

Imagen de Arbër

Que queires que te diga...

Mejor explicado no puede estar. Gracias a esto puedo comprobar que las corrutinas que uso en el codigo de la version del Homebrew que tengo en desarrollo son correctas. Asi que aunque supiese usarlas (más o menos) de antes esto me ha ayudado a aclarar algunas cosillas je, je.

Lo que me sorprende es lo rápido que has captado el lenguaje de Lua y te has animado a hacer tutoriales de nivel avanzado enseguida, creo que le vas cogiendo gustillo y todo ¿eh?

Un saludo carck ;)


-----[[7 años en Scenebeta, con la misma ilusión que la del primer día]]----

Imagen de pspgorrister

Gracias, se agradecen los

Gracias, se agradecen los apoyos, mi intención es explicarlo lo mejor posible para que cualquiera que sepa algo de Lua pueda ponerse con ello.

Sobre el gustillo de Lua, pues la verdad es que es un lenguaje curioso, tiene muy pocas funciones, pero a la vez es muy versátil. Me recuerda aquella frase de Gandalf en el señor de los anillos:

"Los Hobb... esto...Lua es un lenguaje extraordinario, puedes aprender todas sus funciones en un día, que al cabo de 10 años te sorprendera..." xDDD

Pero lo cierto es que es un lenguaje, que de momento no necesito para nada, conozco otros que pueden suplir lo que hace éste, aunque siempre es bueno aprender uno más :-)


LuaDiE: Crea en Lua sin teclear código. Compatible HM7, HMv2, LuaPlayer, LuaDEV y PGE.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.