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:
Desventajas:
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.
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í.
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]]----
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.