Anuncios

Bienvenidos sean a este post, como dijimos en el post anterior un proceso (thread) es basicamente un corutina en Lua, asi que podemos pensar que un corutina es un proceso con el adicional de una interfaz bonita😚 o pensar que un proceso es una corutina con la API de bajo nivel.

Anuncios
Anuncios

Desde la perspectiva de la API de C podemos encontrar util pensar que un proceso es como una pila (la cual en realidad es un proceso) desde un punto de vista de implementacion, cada pila mantiene informacion sobre la llamadas pendientes de un proceso, los parametros y las variables locales de cada llamada, en otras palabras una pila tiene toda la informacion que un proceso necesita para continuar su ejecucion, asi que multiples procesos significan multiples pilas independientes.

Cuando llamamos a la mayoria de las funciones de la API Lua-C esa funcion opera en una pila especifica, por ejemplo lua_pushnumber debe empujar el numero en una pila especifica, lua_pcall necesita una pila de llamada pero como sabe Lua que pila usar? que tenemos que hacer para empujar a un numero en una pila diferente? el secreto esta en el tipo lua_State, el primer argumento de cada una de estas funciones, no solo representa el estado de Lua sino tambien a un proceso dentro de ese estado.

Anuncios

Cuando se crea un estado de Lua, el lenguaje automaticamente crea un nuevo proceso dentro de este estado, lo cual es llamado el proceso principal, dicho proceso nunca es recogido, es liberado junto con el estado cuando se cierra el mismo con lua_close, podemos crear nuevos procesos en un estado llamado a la siguiente funcion:

lua_State *lua_newthread (lua_STate *L);
Anuncios

Esta funcion devuelve un apuntador de lua_State representando el nuevo proceso y tambien empuja el nuevo procceso en la pila, como un valor de tipo «thread», por ejemplo despues de esta declaracion:

L1 = lua_newthread(L);
Anuncios

Tendremos dos procesos, L y L1, ambos referencian internamente al mismo estado de Lua, cada proceso tiene su propio pila, el nuevo proceso L1 comienza con una pila vacia, el proceso viejo L tiene el nuevo proceso en la parte superior de la pila:

printf("%d\n", lua_gettop(L));		-- Devuelve 0
printf("%s\n", lua_typename(L, -1));	-- Devuelve thread
Anuncios

Excepto por el proceso principal, los procesos estan sujetos a la coleccion de basura como cualquier otro objeto de Lua, cuando creas un nuevo proceso el valor empujado en la pila se asegura que el proceso no sea basura, no se deberia usar nunca un proceso que este correctamente anclado en el estado.

Nota: El proceso principal esta internamente anclado asi que nunca debes preocuparte por el.
Anuncios

Cualquier llamado a la API de Lua podria recolectar cualquier proceso no anclado, incluso una llamada usando este proceso, por ejemplo consideremos el siguiente fragmento,

lua_State *L1 = lua_newthread(L);
lua_pop(L, 1);		/* L1 ahora es basura para Lua */
lua_pushstring(L, "holis");
Anuncios

La llamada a lua_pushstring puede disparar al recolector de basura y recolectar a L1, destrozando la aplicacion, al margen del hecho que L1 es en uso, para evitar esto siempre manten una referencia a los procesos que estas usando, por ejemplo en la pila de un proceso anclado o en el registro, una vez que tenemos un nuevo proceso podemos usarlo como el proceso principal, podemos empujar y quitar elementos de su pila, podemos usarlo para llamar funciones y similares, por ejemplo el siguiente codigo hace la llamada f(5) en el nuevo proceso y luego mueve el resultado al proceso viejo:

lua_getglobal(L, "f");
lua_pushinteger(L1, 5);
lua_call(L, 1, 1);
lua_xmove(L1, L, 1);
Anuncios

La funcion lua_xmove es la encargada de mover valores entre dos pilas, la llamada del ejemplo lo que hace es quitar un elemento desde la pila de origen (L1) y lo empuja a la pila de destino (L), para estos usos no necesitamos un nuevo proceso sino que podriamos usar el proceso principal tambien, el punto principal del uso de multiples procesos es para implementar corutinas, asi podemos suspender su ejecucion para retomarlas luego, por eso necesitamos la funcion lua_resume:

int lua_resume(lua_State *L, int narg);
Anuncios

Para continuar corriendo una corutina usamos a lua_resume como usamos a lua_pcall, empujamos la funcion a ser llamada, empuja sus argumentos y llama a lua_resume pasando el numero de argumentos (narg), esta conducta es tambien mucho mas como lua_pcall, con tres diferencias:

Anuncios
  • Primero, lua_resume no posee un parametro para el numero de resultados queridos, siempre devuelve todos los resultados de la funcion llamada
  • Segundo, no tiene un parametro para un manejador de errores
  • Tercero, si la funciones yields estan corriendo, lua_resume devuelve un codigo especial LUA_YIELD y deja el proceso en un estado que puede ser retomado despues

Cuando lua_resume devuelve LUA_YIELD, la parte visible de las pilas de los procesos contiene solo los valores pasados a yield, una llamada a lua_gettop retornara el numero de los valores «yielded», para mover estos valores a otro proceso podemos usar lua_xmove, para retomar un proceso suspendido podes usar lua_resume de nuevo, en tales llamadas Lua asume que todos los valores en la pila son para ser retornadas por la llamada a yield, como un caso peculiar si no tocas las pilas de los procesos entre un retorno de un lua_resume y el proximo retorno, yield devolvera exactamente los valores «yielded», usualmente iniciamos una corutina con una funcion de Lua como su cuerpo, esta funcion de Lua podria llamar otras funciones de Lua y cualquier de estas funciones podria eventualmente ser «yielded», finalizando la llamada a lua_resume, por ejemplo asumamos las siguiente definiciones:

function foo (x) coroutine.yield(10, x) end
function foo1(x) foo(x + 1); return 3 end
Anuncios

Nuestro siguiente paso sera ejecutar este codigo de C:

lua_State *L1 = lua_newthread(L);
lua_getgloba(L, "foo1");
lua_pushinteger(L1, 20);
lua_resume(L1, 1);
Anuncios

La llamada a lua_resume devolvera a LUA_YIELD para señalar que el proceso «yielded», en este punto la pila L1 tiene los valores dados a yield:

printf("%d\n", lua_gettop(L1));		-- Devuelve 2
printf("%d\n", lua_tointeger(L1, 1));	-- Devuelve 10
printf("%d\n", lua_tointeger(L1, 2));	-- Devuelve 21
Anuncios

Cuando retomas el proceso de nuevo este continua desde donde lo detuvimos (la llamada a yield), desde ahi foo devuelve a foo1, que a su vez devuelve a lua_resume:

lua_resume(L1, 0);
printf("%d\n", lua_gettop(L1));		-- Devuelve 1
printf("%d\n", lua_tointeger(L1, 1));	-- Devuelve 3
Anuncios

La segunda llamada a lua_resume devolvera 0, lo cual significa que retorna de forma normal, una corutina tambien podria llamar una funcion de C, asi que una pregunta natural cuando programamos en C es esta;

es posible yield desde una funcion de C?

El Tinchicus

Lua estandar no puede usar a yield entre llamadas de funciones de C, esta restriccion implica que una funcion de C no puede ser suspendida, la unica manera para una funcion de C de tipo yield es cuando regresa, asi que realmente no se suspende a si misma pero su llamador si porque seria una funcion de Lua, y para suspender a este llamador una funcion de C debe llamar a lua_yield de la siguiente manera:

return lua_yield(L, nres);
Anuncios

En este caso nres es el numero de valores en la parte superior de la pila para ser devuelto por el resume correspondiente, cuando el proceso retoma de nuevo el llamador recibira los valores dados a resume, no podemos evitar la limitacion que las funciones de C no puedan usar a yield llamandolos desde dentro de un bucle en Lua, de esta manera despues que la funcion yields y el proceso retoma, el bucle llama a la funcion de nuevo, como un ejemplo asumimos que queremos codificar una funcion que necesita algunos datos, haciendo yield mientras los datos no esta disponible, podriamos escribir la funcion en C de la siguiente forma:

int prim_leer(lua_State *L)
{
	if (nada_pa_leer())
		return lua_yield(L, 0);
	lua_pushstring(L, leer_algun_dato());
	return 1;
}
Anuncios

Si la funcion tiene algun dato para leer, lo lee y devuelve este dato, de lo contrario lo «yieldea», cuando el proceso retoma este no vuelve a prim_leer sino al llamador, para nuestro siguiente paso asumamos que el llamador llama prim_leer en un bucle como este:

function leer()
	local linea
	repeat
		linea = prim_leer()
	until linea
	return linea
end
Anuncios

Cuando prim_leer yields el proceso es suspendido, cuando retoma este continua desde el punto de retorno de prim_leer, la cual es la asignacion a linea, el valor actual asignado sera el valor dado para retomar, asumiendo que ningun valor fue informado linea consigue nil y el bucle continua llamando a prim_leer de nuevo, el proceso completo se repite a si mismo hasta que algun dato es leido o resume pasa un valor no nil.

Anuncios

En resumen, hoy hemos visto procesos multiples, como son en realidad, para que se usan, como se intercambian datos entre ellos, para que realmente lo podriamos usar, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.50

Anuncio publicitario