Anuncios Google

Multitarea en Lua

Tutoriales Avanzados Homebrewes

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.

4.395835
Tu voto: Ninguno Votos totales: 4.4 (96 votos)

Anuncios Google

Comentarios

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 Migueliziosop

Gracias.

Me sirvió de mucho este tema amigo (;.

Imagen de Guillermo_Cornielle

Mil Gracias!

Interesante....ya queria saber como usar esas funciones de corrutinas.....en fin....aunque no sea multitarea real...es algo...Gracias!


Manual del Perfecto Votante Para un voto libre y justo!.

TheGCProjects

Imagen de Monty - Calabato64

Icon0 sin linkear

Icon0 sin linkear

Imagen de moikop

Listo.

Gracias por el aviso.

Imagen de pspgorrister

Re: Icon0 sin linkear

¿Cómor?

Imagen de Monty - Calabato64

No te preocupes mucho, es un

No te preocupes mucho, es un error de edición del editor que te lo pasó a portada.

La verdad que muy útil el

La verdad que muy útil el tuto, y bien explicado con un buen ejemplo. Seguro que más de uno le vendrá bien saber esto para sus creaciones.

También se podría utilizar para el tratamiento de excepciones y errores de homebrew e intentar recuperarse, me equivoco?

Imagen de klozz

orale muy buena opcion :D

orale muy buena opcion :D saludos sigue asi pspgorrister tus tutos son fantasticos n.n

:)

Tutoriales tan bien explicados como este me motivan a aprender lua, quizá despues de terminar de batallar con C XD

Saludos, y gracias ^_^

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.