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!

Documentos relacionados