Erlang concurrente - GPD - Universidad Complutense de Madrid
Transcripción
Erlang concurrente - GPD - Universidad Complutense de Madrid
Erlang concurrente Jaime Sánchez Departamento de Sistemas Informáticos y Computación Universidad Complutense de Madrid Jaime Sánchez. Sistemas Informáticos y Computación, UCM Contenido Introducción Hola concurrencia Entendiendo más de los procesos a través del depurador Un servidor de archivos Profundizando en la concurrencia Jaime Sánchez. Sistemas Informáticos y Computación, UCM ¿Por qué la programación concurrente? (Armstrong) Miro por la ventana y veo una mujer paseando, un coche buscando aparcamiento, un avión en el cielo... todo ello en paralelo! La concurrencia está presente en la actividad cotidiana. Concurrente, simultáneo, paralelo es casi lo mismo, pero en lenguajes de programación no es ası́... Jaime Sánchez. Sistemas Informáticos y Computación, UCM Concurrente Vs. Paralelo Armstrong: con un procesador mono-núcleo, nunca podré correr un programa en paralelo porque solo puedo hacer una cosa (una tarea) en cada instante de tiempo. Pero puedo correr programas concurrentes, compartiendo el tiempo entre diferentes tareas, y manteniendo la ilusión de que las tareas corren en paralelo. Fred Hébert (Learn You Some Erlang for Great Good): Para muchos “Erlangers” la concurrencia se refiere a la idea de tener muchos actores corriendo de modo independiente, pero no necesariamente todos al mismo tiempo. El paralelismo es tener actores corriendo justamente al mismo tiempo. Erlang proporciona un alto nivel de abstracción: en una máquina multicore, el sistema se encarga de distribuir los Sistemas Informáticos y Computación, UCM procesos entreJaime los Sánchez. distintos núcleos. Ventajas del modelo concurrente La programación concurrente se puede utilizar para: I mejorar el rendimiento: permite explotar la potencia de los ordenadores multicore I diseñar sistemas escalables: un programa concurrente es una colección de pequeños procesos independientes, que facilitan la escalabilidad incrementando el número de procesos y/o CPUs I y además tolerantes a fallos: la clave es la independencia de procesos y la redundancia hardware. Un error en un proceso no tiene porqué colgar otros procesos y además permite detectar errores en remoto. I escribir programas claros para controlar aplicaciones del mundo real. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Ideas esenciales la concurrencia en Erlang I Erlang es un lenguaje funcional, etc, etc... orientado a la concurrencia. I La unidad básica de concurrencia en Erlang es el proceso: máquina virtual (ligera) que se puede comunicar con otros procesos exclusivamente a través de mensajes. Un proceso es similar a un objeto en POO: se puede tener un módulo con miles de procesos ejecutando el código de ese módulo. Comparando con POO: modulo ≈ clase I proceso ≈ instancia Erlang trabaja con procesos ligeros: no son procesos del SO, sino de la máquina virtual de Erlang. El modelo de concurrencia de Erlang no depende del SO! y un programa se comportará de modo similar en distintos Jaime Sánchez. Sistemas Informáticos y Computación, UCM SOs (abstracción del SO). Paso de mensajes VS memoria compartida I Problema clásico en programación concurrente: ejecución de código crı́tico de acceso a memoria compartida semáforos, monitores, bloqueo explı́cito.... I En Erlang: no hay memoria compartida. Los procesos se comunican a través de paso de mensajes: I I I El proceso es responsable de los datos La sección crı́tica se encuentra solo en ese proceso No se necesita bloqueo explı́cito. alto nivel de abstracción, simplicidad del modelo, minimización de errores (robustez) (Puede simularse la memoria compartida!) I Para comunicarse con un proceso hay que enviarle un mensaje (los procesos son reactivos a los mensajes). I Paso de mensajes ası́ncrono: cuando se envı́a un mensaje no es necesario quedarse esperando respuesta, sino que se Jaime Sánchez. Sistemas Informáticos y Computación, UCM puede continuar la ejecución. Procesos I Los procesos tienen un código propio y un espacio de datos propio. I Erlang tiene un planificador de procesos que se encarga de dar paso a cada proceso a su debido tiempo, para aprovechar los recursos de la máquina. I Cada proceso dispone de un buzón de mensajes para encolar los mensajes que recibe. Son procesos... no hilos: I I I Cuando un proceso falla y deja su memoria inconsistente, no afecta al resto de procesos, que pueden seguir trabajando normalmente. Los procesos los gestiona Erlang, no el SO (también tiene un coste) Jaime Sánchez. Sistemas Informáticos y Computación, UCM Procesos y mensajes I Los procesos se crean con la función (BIF) spawn: Pid1 = spawn(Modulo,Funcion,[Args,...]) o bien (con una clausura o lambda abstracción): Pid1 = spawn(Fun) Esta llamada devuelve un PID (Process IDentifier) que sirve para interactuar con otros procesos. I El paso de mensajes (send) se hace de la forma: Pid ! Msg I La recepción de mensajes se hace con receive: receive Msg1 -> ...; Msg2 -> ...; ... end I self() devuelve el PID asociado al proceso actual (aquegl Jaime Sánchez. Sistemas Informáticos y Computación, UCM en el que se evalúa esta función). Similar al this en POO. Hola procesos Lanzamos: > spawn(io, format, ["hola mundo!"]). O también: > spawn(fun() −> io:format("hola mundo!") end). O almacenando el PID: > Pid = spawn(fun() −> io:format("hola mundo!~n") end). hola mundo! <0.38.0> > is_process_alive(Pid). Qué devolverá? El proceso vive mientras tiene código que ejecutar, después muere. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Enviando mensajes Creamos un proceso que espera un mensaje y notifica la recepción: > Pid = spawn(fun() −> > receive Any −> > io:format("recibido: ~p~n", [Any]) > end > end). <0.49.0> Envı́amos mensaje > Pid ! "hola". recibido: "hola" "hola" Jaime Sánchez. Sistemas Informáticos y Computación, UCM Enviando remitente Idem que antes, pero con acuse de recibo: > Pid = spawn(fun() −> > receive > {P,M} −> > io:format("recibido: ~p~n", [M]), > P ! "adios" > end > end). <0.40.0> > Pid ! {self (), "hola"}, > receive > Msg −> > io:format("retorno: ~p~n", [Msg]) > end. recibido: "hola" retorno: "adios" Sánchez. Sistemas y Computación, UCM ¿Quién recibe Jaime el mensaje Msg? Informáticos ¿Qué proceso? Un módulo para hola mundo Módulo para gestionar proceso de saludo. −compile(export_all). init() −> spawn(fun()−> procesaMensaje() end). procesaMensaje() −> receive {saluda,Persona} −> io:format("Hola ~p~n",[Persona]); Msg −> io:format("Error: no entiendo ~p~n",[Msg]) end. > c(saludador). {ok,saludador} > P = saludador:init(). <0.92.0> > P ! {saluda, pedro}. Hola pedro {saluda,pedro} Jaime Sánchez. Sistemas Informáticos y Computación, UCM Debbuger: un depurador para Erlang. Guı́a rapidı́sima Es un entorno gráfico para depurar programas interpretados en Erlang: admite breakpoints, ejecución paso a paso, visualización de valores de variables... I Para depurar un módulo, hay que haberlo compilado previamente con la opción debug info > c(ejemplo,[debug info]). I Arrancar depurador: > debugger:start(). Con esto se arranca la ventana de monitorización, desde donde se ven los procesos en ejecución, su estado y se puede acceder a su código (si es interpretado). I Activar opciones First Call, On Break, On Exit Jaime Sánchez. Sistemas Informáticos y Computación, UCM Debbuger II I Seleccionar módulos a depurar desde la ventana Module->Interpret... I Lanzar la expresión a depurar desde el intérprete de Erlang. Se pueden poner los breakpoints oportunos (en lı́neas ejecutables) y depurar casi como un un depurador estándar. I La ventana Monitor muestra los procesos en ejecución I Ver http://www.erlang.org/doc/apps/debugger/ debugger_chapter.html Jaime Sánchez. Sistemas Informáticos y Computación, UCM Aplicación prototipo: un servidor de archivos afile server.erl −export([start/1, loop/1]). start(Dir) −> spawn(afile_server, loop, [Dir]). loop(Dir) −> receive {Client, list_dir} −> Client ! {self (), file:list_dir(Dir)}; {Client, {get_file, File}} −> Full = filename:join(Dir, File), Client ! {self (), file:read_file(Full)} end, loop(Dir). Jaime Sánchez. Sistemas Informáticos y Computación, UCM I Se crea un proceso spawn(afile server, loop, [Dir]) I Este proceso ejecuta la función loop que queda a la espera de recibir un mensaje (un comando/orden para el servidor de archivos). Esto no deja colgado el sistema! (lo harı́a en un lenguaje secuencial). I Cuando recibe el mensaje {Client, ...} se realiza la operación oportuna y responde al cliente. Client es el remitente del mensaje recibido, que se utiliza para responder a ese remitente. I El mensaje que se envı́a al cliente, a su vez incluye self(), otra vez a modo de remitente. En este caso, no es estrictamente necesario, pero podrı́a servir para comprobar la procedencia del mensaje. I Por último, se evalúa la llamada recursiva: el proceso Jaime Sánchez. Sistemas Informáticos y Computación, UCM vuelve a quedar a la espera de recibir otro mensaje. Más detalles I file:list dir(Dir) devuelve la lista de archivos que contiene el directorio Dir I Client ! {self(), file:list dir(Dir)} envı́a al proceso Client (es un PID) un mensaje, con el remitente self() y la lista de archivos del directorio Dir. I filename:join(Dir,File) concatena el path indicado por Dir con el nombre de archivo File. I Que hace Client ! {self(), file:read file(Full)}? Jaime Sánchez. Sistemas Informáticos y Computación, UCM I Ejecutar: 1> c(afile_server). {ok,afile_server} 2> FileServer = afile_server:start("."). <0.47.0> 3> FileServer ! {self(), list_dir}. {<0.31.0>,list_dir} 4> receive X −> X end. I <0.47.0> es el valor de la variable FileServer, i.e., el PID del proceso que se crea con la función start en el módulo afile server: el PID del servidor de archivos. I Qué es <0.31.0>? I A qué proceso corresponde el último receive? I Se podrı́a poner self() ! Jaime Sánchez. Sistemas Informáticos y Computación, UCM receive X -> X end.? Información sobre procesos Con el programa anterior, si hacemos 2> FileServer = afile_server:start("."). <0.47.0> 3> FileServer ! {self(), list_dir}. {<0.31.0>,list_dir} 4> process_info(self()). [{current_function,{erl_eval,do_apply,6}}, {initial_call,{erlang,apply,2}}, {status,running}, {message_queue_len,1}, {messages,[{<0.40.0>, {ok,["afile_server.erl","afile_client.erl", "afile_server2.beam","afile_server.beam", "afile_server2.erl"]}}]}, ... {total_heap_size,4184}, {heap_size,2586}, {stack_size,24}, Jaime Sánchez. Sistemas Informáticos y Computación, UCM {reductions,5578}, ...] Un cliente asociado al servidor de archivos −module(afile_client). −export([ls/1, get_file/2]). ls(Server) −> Server ! {self (), list_dir}, receive {Server, FileList} −> FileList end. get_file(Server, File) −> Server ! {self (), {get_file, File}}, receive {Server, Content} −> Content end. Jaime Sánchez. Sistemas Informáticos y Computación, UCM I El propósito del cliente es esconder detalles del protocolo de comunicación (abstracción). I El usuario del cliente tiene a disposición dos funciones ls y get file para interactuar fácilmente con el el servidor de archivos. I Observar la simetrı́a entre el código del cliente y el servidor: emparejamiento de send y receive. I Naturalmente este diseño de servidor se puede mejorar hasta convertirlo en un sistema real (con sockets, etc. Ver Armstrong) Jaime Sánchez. Sistemas Informáticos y Computación, UCM 1> c(afile_server). {ok,afile_server} 2> c(afile_client). {ok,afile_client} 3> FileServer = afile_server:start("."). <0.43.0> 4> afile_client:get_file(FileServer,"missing"). {error,enoent} 5> afile_client:get_file(FileServer,"afile_server.erl"). {ok,<<"-module(afile_server).\n-export([start/1])....} Jaime Sánchez. Sistemas Informáticos y Computación, UCM Concurrencia en Erlang. Inspiración I I I I I I I I Modelo de concurrencia inspirado en el mundo real: el mundo corre en paralelo. Nuestra mente funciona en paralelo (a través de la amı́gdala, entre otros centros nerviosos) ... pero además: nuestras mentes no tienen memoria compartida; nos comunicamos a través de mensajes. Un programa en Erlang es una colección de procesos. Cada proceso tiene su propia memoria y para cambiarla hay comunicación a través de mensajes (y observación de respuestas: mensajes de confirmación). Este modelo facilita el mantenimiento y la escalabilidad de los programas en Erlang. Los procesos pueden estar enlazados: cuando un proceso muere, informa de la causa (división entre cero, acceso al último elemento en una lista vacı́a...)... Jaime Sánchez. Sistemas Informáticos y Computación, UCM y otro proceso puede ”arreglar” la situación. Tres primitivas para procesos I spawn: crea un proceso (paralelo) I send (!): envı́a un mensaje a un proceso I receive: recibe mensajes Jaime Sánchez. Sistemas Informáticos y Computación, UCM En mayor detalle I Pid = spawn(Mod, Func, Args): crea un nuevo proceso concurrente que evalúa apply(Mod, Func, Args) y devuelve un Process IDentifier. La función Fun debe haber sido exportada en el modulo Mod con aridad correspondiente a la longitud de la lista Args. Cuando se crea un nuevo proceso, se utiliza la última versión del módulo que define el código. I Pid = spawn(Func): crea un nuevo proceso concurrente que evalúa Fun(); utiliza el valor actual de la función que está siendo evaluada y no tiene que haber sido exportada desde el módulo. ¿Spawning con MFAs o Funs? MFA asegura que los procesos en ejecución se actualizarán correctamente si cambia (se actualiza y se compila) el código del módulo que los define mientras está Jaime Sánchez. Sistemas Informáticos y Computación, UCM en ejecución: Hot Code Swapping. En mayor detalle I Pid ! Message: envı́a Message al proceso con identificador Pid. I I I I I I El envı́o es ası́ncrono: el que envı́a el mensaje no tiene que esperar, sino que continúa con su ejecución. Se hace una copia de Message para evitar compartición de memoria. ! es el operador de envı́o (send) Pid ! M devuelve M. Pid1 ! Pid2 ! ... ! Msg significa: envı́a el mensaje Msg a todos los procesos Pid1, Pid2, ... Se puede simular el paso de mensajes sı́ncrono. Jaime Sánchez. Sistemas Informáticos y Computación, UCM I receive ... end: recibe un mensaje que ha sido enviado al proceso. Sintaxis: receive Pattern1 [when Guard1] −> Expressions1; Pattern2 [when Guard2] −> Expressions2; ... end Cuando llega un mensaje hace ajuste de patrones para buscar la expresión a evaluar. Si no hay ningún patrón que case, el mensaje se guarda para procesamiento posterior y el proceso se queda esperando nuevos mensajes. Cada proceso tiene asociado un buzón, que se crea junto con el proceso. Cuando se envı́a un mensaje al proceso, el mensaje se pone en el buzón de dicho proceso. Solo se examina el buzón Jaimeun Sánchez. Sistemas Informáticos y Computación, UCM cuando se evalúa receive. receive liga (asigna) variables! 1> self() ! hola, receive X −> X end. hola X queda asignada 2> X. hola Y esto significa: 3> self() ! adios, receive X −> X end. es como hacer 3> self() ! adios, receive hola −> hola end. Jaime Sánchez. Sistemas Informáticos y Computación, UCM y el proceso queda esperando... receive en programa No es buena idea hacer algo como: f()−> spawn(fun()−> receive X −> io:format("hola ~p~n",[X]) end, receive X −> io:format("adios ~p~n",[X]) end end). (Ver ../ejemplos/receive/pru.erl) Jaime Sánchez. Sistemas Informáticos y Computación, UCM Un servidor de áreas −module(area_server). −export([loop/0]). loop() −> receive {rectangle, Width, Ht} −> io:format("Area of rectangle is ~p~n",[Width ∗ Ht]), loop(); {square, Side} −> io:format("Area of square is ~p~n", [Side ∗ Side]), loop() end. (ver ejemplos/cap12-procesos/area server.erl) Jaime Sánchez. Sistemas Informáticos y Computación, UCM Probando en la shell Ahora podemos crear un proceso que evalúe loop en la shell: 1> Pid = spawn(area_server0, loop, []). <0.36.0> 2> Pid ! {rectangle, 6, 10}. Area of rectangle is 60 {rectangle,6,10} 3> Pid ! {square, 12}. Area of square is 144 {square, 12} Jaime Sánchez. Sistemas Informáticos y Computación, UCM Cliente-Servidor En Erlang el cliente y el servidor en una arquitectura cliente-servidor son simplemente procesos separados y se utiliza el paso de mensajes para comunicarlos. Ambos pueden correr en la misma máquina o en diferentes máquinas. Cliente y servidor se refieren solo a los roles de esos procesos. El cliente siempre inicia un cómputo mediante el envı́o de una solicitud al servidor. El servidor computa una respuesta y envı́a una respuesta al cliente Enviar remitente. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Refinando el servidor de áreas Más abstracción. Añadimos remitente en el mensaje y respondemos al mismo: eliminamos salida en pantalla. −module(area_server2). −export([loop/0,rpc/2]). loop() −> receive {From, {rectangle, Width, Ht}} −> From ! Width ∗ Ht, loop(); {From, {circle, R}} −> From ! 3.14159 ∗ R ∗ R, loop(); {From, Other} −> From ! {error,Other}, loop() end. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Refinando II Añadimos además una función rpc (remote procedure call) para encapsular el paso de mensajes: rpc(Pid, Request) −> Pid ! {self (), Request}, receive Response −> Response end. Queda un problema en rpc: supongamos que enviamos un mensaje al servidor y esperamos respuesta; entre tanto llega un mensaje de otro proceso distinto al servidor y lo interpretamos como la respuesta esperada. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Refinando III Podemos arreglarlo añadiendo siempre el remitente en el envı́o de mensajes y comprobándolo en la recepción: loop() −> receive {From, ... } −> From ! {self (), ... } loop() ... end. rpc(Pid, Request) −> Pid ! {self (), Request}, receive {Pid, Response} −> Response end. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Refinando IV Un último refinamiento: ocultar rpc y spawn dentro del módulo, a través de funciones: −module(area_server_final). −export([start/0, area/2, loop/0]). start() −> spawn(area_server_final, loop, []). area(Pid, What) −> rpc(Pid, What). rpc(Pid, Request) −> Pid ! {self (), Request}, receive {Pid, Response} −> Response end. ... Jaime Sánchez. Sistemas Informáticos y Computación, UCM Probando en la shell Ahora podemos ejecutar: 1> Pid = area_server_final:start(). <0.36.0> 2> area_server_final:area(Pid, {rectangle, 10, 8}). 80 3> area_server_final:area(Pid, {circle, 4}). 50.2654 Jaime Sánchez. Sistemas Informáticos y Computación, UCM Selección de mensajes recibidos En cualquier orden se hace: I El proceso A envı́a el mensaje msg1 al proceso C: C ! msg1 I El proceso B envı́a el mensaje msg2 al proceso C: C ! msg2 C puede recibir de varias formas: receive msg1 > ... end, receive msg2 -> ... end, ... Recepción selectiva de mensajes receive Msg -> ... ; ... Recepción del que llegue Jaime Sánchez. Sistemas Informáticos y Computación, UCM Información de procesos En la shell: I i(): devuelve información sobre los procesos activos. I processes() devuelve la lista de procesos activos. I process info(Pid) devuelve información del proceso asociado a Pid (cola de mensajes, procesos enlazados, memoria usada por el proceso...). La llamada es de la forma process info(pid(0,33,0)). I process info(Pid,Items) devuelve los Items de información del proceso asociado a Pid Véase http: //www.erlang.org/doc/man/erlang.html#process_info-2 Jaime Sánchez. Sistemas Informáticos y Computación, UCM Coste creación procesos Lo que hemos visto hasta ahora incita a crear procesos y más procesos... pero qué precio (en eficiencia) tenemos que pagar por ello? Para medir tiempo (Armstrong): statistics(runtime), statistics(wall_clock), << codigo aquı́ >> {_, Time1} = statistics(runtime), {_, Time2} = statistics(wall_clock), U1 = Time1 ∗ 1000, U2 = Time2 ∗ 1000, io:format("Code time=~p (~p) microseconds~n", [U1,U2]). (otra forma: timer:tc(foo,qsort,[L]).) La creación de procesos es muy barata. Ver ejemplo cap12-procesos/procesos.erl conInformáticos depurador. Jaime Sánchez. Sistemas y Computación, UCM Afinando la recepción de mensajes: timeouts Un receive puede quedar indefinidamente esperando un mensaje: por error en el programa, porque el proceso que debı́a enviar el mensaje haya muerto, etc... Podemos acotar el tiempo de espera con la construcción after: receive Pattern1 [when Guard1] −> Expressions1; Pattern2 [when Guard2] −> Expressions2; ... after Time −> Expressions end Si no llega un mensaje que ajuste con los patrones del receive antes de Time milisegundos (desde que comienza el receive), el proceso deja de esperar mensaje (sale del receive) y ejecuta Expressions Jaime Sánchez. Sistemas Informáticos y Computación, UCM Timeouts: suspensión de ejecución Función sleep(T): suspende la ejecución del proceso durante T milisegundos. sleep(T) −> receive after T −> true end. Ver ejemplo ejemplos/timeouts/textoRetardo Jaime Sánchez. Sistemas Informáticos y Computación, UCM Timeouts: vaciado del buzón de mensajes Un timeout de 0 ms. provoca que el cuerpo del after se ejecute inmediatamente, pero antes se procesan todos los mensajes del buzón que ajusten con los patrones del receive. Se puede hacer un flush buffer del siguiente modo: flush_buffer() −> receive _Any −> flush_buffer() after 0 −> true end. pero... hace falta realmente el after para conseguir este efecto? Sı́: sin el timeout, flush buffer se quedarı́a suspendido para siempre (en el receive) y no acabarı́a cuando el buzón estuviese vacı́o. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Timeouts: priorizar mensajes Utilizando la idea anterior, podemos priorizar mensajes. priority_receive() −> receive {alarm, X} −> {alarm, X} after 0 −> receive Any −> Any end end. se procesa (recorre) el buzón en buscando mensajes prioritarios (de la forma {alarm, X}) I si se encuentra alguno, se procesa inmediatamente (y se siguen buscando prioritarios) I cuando no quedan prioritarios, se procesa el primero del buzón: el receive interno espera cualquier mensaje ¿Qué ocurrirı́a sin el after? (Con Any como patrón después de {alarm,X}) (siJaime en Sánchez. el buzón hay [hola,{alarm,X}] se procesa Sistemas Informáticos y Computación, UCM antes hola) I Timeouts: evaluación retardada Podemos evaluar una función con retardo. start(Time, Fun): evalúa la función Fun (función sin argumentos) después de Time ms. Además, se puede cancelar la ejecución antes de empezar. −module(stimer). −export([start/2, cancel/1]). start(Time, Fun) −> spawn(fun() −> timer(Time, Fun) end). cancel(Pid) −> Pid ! cancel. timer(Time, Fun) −> receive cancel −> void after Time −> Fun() end. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Receive en profundidad I ¿Cómo funciona? receive Pattern1 [when Guard1] −> Expressions1; Pattern2 [when Guard2] −> Expressions2; ... after Time −> ExpressionsTimeout end Jaime Sánchez. Sistemas Informáticos y Computación, UCM Receive en profundidad II 1. Al entrar en el receive arranca un timer (si hay construcción after) 2. Se procesa el primer mensaje del buzón: se prueba con Pattern1, Pattern2... Si casa, se elimina del buzón y se procesa la expresión correspondiente 3. Si no casa, se elimina del buzón y se pone en una save queue. Se procesa el segundo mensaje del buzón, etc, hasta que casa o se han examinado todos los mensajes del buzón. 4. Si ningún mensaje casa, el proceso queda suspendido hasta que llegue un nuevo mensaje y el planificador vuelva a seleccionar este proceso. No se tocan los mensajes de save queue, solo se trata el mensaje recién llegado). 5. Tan pronto como casa un mensaje, todos los mensajes de save queue reentran en el buzón en el orden en el que llegaron al proceso. Si habı́a timer, se resetea. 6. Si el timer se agota esperando un mensaje, se evalúa Jaime Sánchez. Sistemas y Computación, UCMqueue ExpressionsTimeout y losInformáticos mensajes de save reentran en el buzón en el orden de llegada al proceso. Timeouts: más prioridades Podrı́amos pensar: si un mensaje no ajusta con ningún patrón para qué ponerlo en una “save queue”... nunca va ajustar en el futuro (?) porque puede haber varios bloques receive, por ejemplo para manejar prioridades (los que llevan tag low prio pasan a la save queue): receive {high_prio, Msg} −> process(Msg); after 0 −> receive {low_prio, Msg} −> process(Msg) end end. Para saber más: Priority Messaging made Easy. A Selective Generic Server, Jan Henry Nyström, Erlang’07, ACM 2007. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Procesos registrados I Para mandar un mensaje a un proceso necesitamos el PID de dicho proceso. Para que otro proceso conozca ese PID, habrı́a que enviarlo explı́citamente (y esto con todos PIDs de los procesos con los que se comunica). I Esto es muy seguro: hay mucho control sobre qué procesos pueden comunicar con otro. I Pero Erlang proporciona una forma de hacer público el nombre de los procesos y facilitar la comunicación sin tener que propagar el Pid en los mensajes o como argumento. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Procesos registrados II Estas son las BIFs: I register(AnAtom, Pid): registra el proceso PID con el nombre AnAtom; falla si AnAtom ya se utilizado para registrar otro proceso I unregister(AnAtom): elimina el registro asociado con AnAtom (cuando un proceso muere, se hace el unregister automáticamente). I whereis(AnAtom) −> Pid | undefined: determina si AnAtom es un nombre registrado y devuelve el PID correspondiente; devuelve undefined si no está registrado. I registered() −> [AnAtom::atom()]: devuelve la lista de procesos registrados en el sistema. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Un reloj con proceso registrado −module(clock). −export([start/2, stop/0]). start(Time, Fun) −> register(clock, spawn(fun() −> tick(Time, Fun) end)). stop() −> clock ! stop. tick(Time, Fun) −> receive stop −> void after Time −> Fun(), tick(Time, Fun) end. (ver ejemplos/register/clocl.erl) Jaime Sánchez. Sistemas Informáticos y Computación, UCM Arrancando el reloj > clock:start(1000, fun() −> io:format("tick~n",[]) end). true tick tick 3> clock:stop(). ¿que pasa aquı́?: 4> clock:start(3000, fun() −> io:format("tick~n",[]) end). true tick tick 5> clock:start(2000, fun() −> io:format("tick~n",[]) end). ∗∗ exception error: bad argument ... tick tick 6> clock:stop(). stop tick Jaime Sánchez. Sistemas Informáticos y Computación, UCM tick Recursión de cola I Volvamos al ejemplo de las áreas, a la función loop: loop() −> receive {From, {rectangle, Width, Ht}} −> From ! {self (), Width ∗ Ht}, loop(); {From, {circle, R}} −> From ! {self (), 3.14159 ∗ R ∗ R}, loop(); {From, Other} −> From ! {self (), {error,Other}}, loop() end. Las llamadas a loop son lo último que se hace en cada bloque de código excluyente, i.e., lo último que hace loop es invocarse a sı́ mismo, no se ejecuta código después de la llamada recursiva recursión de cola (o final). ¿Se podrı́a poner llamada a loop fuera de los UCM casos del Jaime la Sánchez. Sistemas Informáticos y Computación, receive? Recursión de cola II Erlang (y casi todos los compiladores) pueden compilar la recursión de cola de manera muy eficiente, sin consumir espacio en la pila de llamadas (ni tiempo en crear y destruir registros de activación). I La recursión de cola es prácticamente un requisito imperativo en funciones asociadas a procesos de recepción de mensajes (que previsiblemente se ejecutarán un enorme número de veces). Para Armstrong, este código es incorrecto loop() −> receive {From, {rectangle, Width, Ht}} −> From ! {self (), Width ∗ Ht}, loop(); otroCodigo... ... Jaime Sánchez. Sistemas Informáticos y Computación, UCM La plantilla Armstrong −module(ctemplate). −compile(export_all). start() −> spawn(?MODULE, loop, []). rpc(Pid, Request) −> Pid ! {self (), Request}, receive {Pid, Response} −> Response end. loop() −> receive Any −> io:format("Received:~p~n",[Any]), loop() end. Jaime Sánchez. Sistemas Informáticos y Computación, UCM Y este código va creciendo... hasta tener facebook o whatsapp!