2011-02-27 7 views
5

que estoy haciendo este ejercicio de la erlang.org course:Ejercicio de concurrencia del curso Erlang: ¿Se puede mejorar mi respuesta?

2) escribir una función que comienza N procesos en un anillo, y envía un mensaje M veces alrededor de todos los procesos en el anillo. Después de que se hayan enviado los mensajes , los procesos deben terminar con elegancia.

Aquí es lo que he llegado con:

-module(ring). 
-export([start/2, node/2]). 

node(NodeNumber, NumberOfNodes) -> 
    NextNodeNumber = (NodeNumber + 1) rem NumberOfNodes, 
    NextNodeName = node_name(NextNodeNumber), 
    receive 
    CircuitNumber -> 
     io:format("Node ~p Circuit ~p~n", [NodeNumber, CircuitNumber]), 
     LastNode = NodeNumber =:= NumberOfNodes - 1, 
     NextCircuitNumber = case LastNode of 
          true -> 
          CircuitNumber - 1; 
          false -> 
          CircuitNumber 
         end, 
     if 
     NextCircuitNumber > 0 -> 
      NextNodeName ! NextCircuitNumber; 
     true -> 
      ok 
     end, 
     if 
     CircuitNumber > 1 -> 
      node(NodeNumber, NumberOfNodes); 
     true -> 
      ok 
     end 
    end. 

start(NumberOfNodes, NumberOfCircuits) -> 
    lists:foreach(fun(NodeNumber) -> 
        register(node_name(NodeNumber), 
          spawn(ring, node, [NodeNumber, NumberOfNodes])) 
       end, 
       lists:seq(0, NumberOfNodes - 1)), 
    node_name(0) ! NumberOfCircuits, 
    ok. 

node_name(NodeNumber) -> 
    list_to_atom(lists:flatten(io_lib:format("node~w", [NodeNumber]))). 

Aquí está su salida:

17> ring:start(3, 2). 
Node 0 Circuit 2 
ok 
Node 1 Circuit 2 
Node 2 Circuit 2 
Node 0 Circuit 1 
Node 1 Circuit 1 
Node 2 Circuit 1 

si realmente sabía Erlang, sería podía hacer de manera diferente para mejorar el código? Y específicamente:

  • ¿Hay alguna alternativa a especificar una cláusula "verdadera" de no hacer nada en las dos últimas sentencias if?

  • ¿Debo terminar elegantemente? ¿Se requiere alguna acción especial al finalizar un proceso que se registró?

Respuesta

6

Bienvenido a Erlang! Espero que lo disfrutes tanto como yo.

¿Hay alguna alternativa a especificar una cláusula "verdadera" de no hacer nada en las dos últimas sentencias if?

Puede simplemente dejar esto. Me encontré con este código:

if NextCircuitNumber > 0 -> 
    NextNodeName ! NextCircuitNumber 
end, 
if CircuitNumber > 1 -> 
    node(NodeNumber, NumberOfNodes) 
end 

y funcionó para mí.

¿De verdad estoy terminando graciosamente? ¿Se requiere alguna acción especial al finalizar un proceso que se registró?

Sí, lo es. Puede verificar esto ejecutando el comando i().. Esto le mostrará la lista de procesos, y si sus procesos registrados no terminaran, vería muchos de sus procesos registrados como node0, node1, etc. Tampoco podría ejecutar su programa una segunda vez, porque sería un error intentar registrar un nombre ya registrado.

En cuanto a otras cosas que podría hacer para mejorar el código, no hay mucho porque su código está básicamente bien. Una cosa que podría hacer es dejar de lado la variable NextNodeName. Puede enviar un mensaje directamente al node_name(NextNodeNumber) y eso funciona.

Además, probablemente podría hacer un poco más de coincidencia de patrones para mejorar las cosas. Por ejemplo, un cambio que hice mientras jugaba con su código fue generar los procesos al pasar el número del último nodo (NumberOfNodes - 1), en lugar de pasar el NumberOfNodes.Entonces, pude coincidencia de patrones en mi cabecera node/2 función como esta

node(LastNode, LastNode) -> 
    % Do things specific to the last node, like passing message back to node0 
    % and decrementing the CircuitNumber 
node(NodeNumber, LastNode) -> 
    % Do things for every other node. 

que me permitió limpiar parte de la lógica case y if en su función de node y hacer que todo sea un poco más ordenado.

Espero que ayude, y buena suerte.

+0

¡buenos comentarios aquí! –

5

Permite caminar a través del código:

-module(ring). 
-export([start/2, node/2]). 

El nombre node es uno evito porque un nodo() en Erlang tiene la connotación de un Erlang máquina virtual que se ejecuta en una máquina - por lo general varios nodos ejecutan en varias máquinas . Prefiero llamarlo ring_proc o algo similar.

node(NodeNumber, NumberOfNodes) -> 
    NextNodeNumber = (NodeNumber + 1) rem NumberOfNodes, 
    NextNodeName = node_name(NextNodeNumber), 

Esto es lo que estamos tratando de desovar, y se obtiene un número al siguiente nodo y el nombre del siguiente nodo. Veamos node_name/1 como un interludio:

node_name(NodeNumber) -> 
    list_to_atom(lists:flatten(io_lib:format("node~w", [NodeNumber]))). 

Esta función es una mala idea. Necesitará un nombre local que necesite ser un átomo, por lo que creó una función que puede crear tales nombres arbitrarios. La advertencia aquí es que la tabla de átomos no es basura recolectada y limitada, por lo que debemos evitarla si es posible. El truco para resolver este problema es pasar los pids en su lugar y construir el anillo en reversa. El proceso final será luego atar el nudo del anillo:

mk_ring(N) -> 
    Pid = spawn(fun() -> ring(none) end), 
    mk_ring(N, Pid, Pid). 

mk_ring(0, NextPid, Initiator) -> 
    Initiator ! {set_next, NextPid}, 
    Initiator; 
mk_ring(N, NextPid, Initiator) -> 
    Pid = spawn(fun() -> ring(NextPid) end), 
    mk_ring(N-1, Pid, Initiator). 

y luego podemos reescribir su función de arranque:

start(NumberOfNodes, NumberOfCircuits) -> 
    RingStart = mk_ring(NumberOfNodes) 
    RingStart ! {operate, NumberOfCircuits, self()}, 
    receive 
    done -> 
     RingStart ! stop 
    end, 
    ok. 

El código anillo es entonces algo a lo largo de las líneas de:

ring(NextPid) -> 
    receive 
    {set_next, Pid} -> 
     ring(Pid); 
    {operate, N, Who} -> 
     ring_ping(N, NextPid), 
     Who ! done, 
     ring(NextPid); 
    ping -> 
     NextPid ! ping, 
     ring(NextPid); 
    stop -> 
     NextPid ! stop, 
     ok 
    end. 

Y al fuego algo alrededor del anillo N veces:

ring_ping(0, _Next) -> ok; 
ring_ping(N, Next) -> 
    Next ! ping 
    receive 
    ping -> 
     ring_ping(N-1, Next) 
    end. 

(Ninguno de estos códigos ha sido probado por cierto, por lo que puede ser bastante erróneo).

En cuanto al resto de su código:

receive 
    CircuitNumber -> 
    io:format("Node ~p Circuit ~p~n", [NodeNumber, CircuitNumber]), 

Me marcar los CircuitNumber con algún átomo: {run, CN}.

LastNode = NodeNumber =:= NumberOfNodes - 1, 
    NextCircuitNumber = case LastNode of 
         true -> 
         CircuitNumber - 1; 
         false -> 
         CircuitNumber 
        end, 

Esto se puede hacer con un if:

NextCN = if NodeNumber =:= NumberOfNodes - 1 -> CN -1; 
       NodeNumber =/= NumberOfNodes - 1 -> CN 
      end, 

La siguiente parte aquí:

if 
    NextCircuitNumber > 0 -> 
     NextNodeName ! NextCircuitNumber; 
    true -> 
     ok 
    end, 
    if 
    CircuitNumber > 1 -> 
     node(NodeNumber, NumberOfNodes); 
    true -> 
     ok 
    end 

necesita el caso true, a menos que nunca lo golpeó. El proceso se bloqueará si no coincide nada en el if. A menudo es posible volver a cablear el código para no confiar tanto en las construcciones de conteo, como el código anterior de pistas de minas.


Se pueden evitar varios problemas con este código. Un problema con el código actual es que si algo se bloquea en el anillo, se rompe. Podemos usar spawn_link en lugar de spawn para unir el anillo, por lo que dichos errores destruirán todo el anillo. Además, nuestra función ring_ping se bloqueará si se envía un mensaje mientras el anillo está funcionando. Esto puede ser aliviado, la forma más simple es probablemente alterar el estado del proceso de anillo de modo que sepa que está funcionando actualmente y doblar ring_ping en ring. Finalmente, probablemente también deberíamos vincular el engendro inicial para que no terminemos con un anillo grande que esté en vivo pero que nadie tenga referencia. Tal vez podríamos registrar el proceso inicial por lo que es fácil agarrar el anillo más tarde.

La función start también es mala de dos maneras. En primer lugar, debemos usar make_ref() para etiquetar un mensaje único y recibir la etiqueta, por lo que otro proceso no puede ser siniestro y simplemente envíe done al proceso de inicio mientras el anillo funciona. Probablemente también deberíamos agregar un monitor en el anillo, mientras está funcionando. De lo contrario, nunca se nos informará, se debe bloquear el anillo mientras esperamos el mensaje done (con etiqueta). OTP hace ambas cosas en sus llamadas sincrónicas por cierto.


Finalmente, por último: No, usted no tiene que limpiar un registro.

3

Mis colegas han hecho algunos puntos excelentes. También me gustaría mencionar que la intención inicial del problema se evita registrando los procesos en lugar de crear un anillo. Aquí hay una posible solución:

-module(ring). 
-export([start/3]). 
-record(message, {data, rounds, total_nodes, first_node}). 

start(TotalNodes, Rounds, Data) -> 
    FirstNode = spawn_link(fun() -> loop(1, 0) end), 
    Message = #message{data=Data, rounds=Rounds, total_nodes=TotalNodes, 
         first_node=FirstNode}, 
    FirstNode ! Message, ok. 

loop(Id, NextNode) when not is_pid(NextNode) -> 
    receive 
     M=#message{total_nodes=Total, first_node=First} when Id =:= Total -> 
      First ! M, 
      loop(Id, First); 
     M=#message{} -> 
      Next = spawn_link(fun() -> loop(Id+1, 0) end), 
      Next ! M, 
      loop(Id, Next) 
    end; 
loop(Id, NextNode) -> 
    receive 
     M=#message{rounds=0} -> 
      io:format("node: ~w, stopping~n", [Id]), 
      NextNode ! M; 
     M=#message{data=D, rounds=R, total_nodes=Total} -> 
      io:format("node: ~w, message: ~p~n", [Id, D]), 
      if Id =:= Total -> NextNode ! M#message{rounds=R-1}; 
       Id =/= Total -> NextNode ! M 
      end, 
      loop(Id, NextNode) 
    end. 

Esta solución utiliza registros. Si no está familiarizado con ellos, lea todo sobre ellos here.

Cada nodo se define mediante una función loop/2. La primera cláusula de loop/2 trata sobre la creación del anillo (la fase de construcción), y la segunda cláusula trata sobre la impresión de los mensajes (la fase de datos). Tenga en cuenta que todas las cláusulas terminan en una llamada al loop/2, excepto la cláusula rounds=0, que indica que el nodo ha terminado su tarea y debería morir. Esto es lo que se entiende por terminación elegante. También tenga en cuenta el truco utilizado para decirle al nodo que está en la fase de compilación: NextNode no es un pid, sino más bien un número entero.

Cuestiones relacionadas