Erlang课程并发练习:我的答案可以改进吗?

Way*_*rad 5 concurrency erlang

我正在从erlang.org课程中做这个练习:

2)编写一个在环中启动N个进程的函数,并在环中的所有进程周围发送M次消息.发送消息后,进程应正常终止.

这是我想出的:

-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]))).
Run Code Online (Sandbox Code Playgroud)

这是它的输出:

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
Run Code Online (Sandbox Code Playgroud)

如果我真的知道Erlang,我可以做不同的改进这段代码吗?具体而言:

  • 在最后两个if语句中是否有指定do-nothing"true"子句的替代方法?

  • 我确实优雅地终止了吗?结束已注册的流程是否需要采取特殊措施?

irr*_*ate 6

欢迎来到Erlang!我希望你能像我一样享受它.

在最后两个if语句中是否有指定do-nothing"true"子句的替代方法?

你可以把它们关掉.我用这个运行你的代码:

if NextCircuitNumber > 0 ->
  NextNodeName ! NextCircuitNumber
end,
if CircuitNumber > 1 ->
  node(NodeNumber, NumberOfNodes)
end
Run Code Online (Sandbox Code Playgroud)

它对我有用.

我确实优雅地终止了吗?结束已注册的流程是否需要采取特殊措施?

是的,你是.您可以通过运行该i().命令来验证这一点.这将显示您的进程列表,如果你注册的进程并没有结束,你会看到很多遗留喜欢你的注册流程node0,node1等等.你也将无法运行程序第二次,因为它尝试注册已注册的名称会出错.

至于你可以做的其他事情来改进代码,没有太多因为你的代码基本上没问题.我可能做的一件事就是放弃NextNodeName变量.您可以直接发送消息node_name(NextNodeNumber)并且可以正常工作.

此外,您可以进行更多模式匹配以改进.例如,我在使用您的代码时所做的一个更改是通过传入最后一个节点的编号来生成进程(NumberOfNodes - 1),而不是传递NumberOfNodes.然后,我可以node/2像我这样在我的函数头中进行模式匹配

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.
Run Code Online (Sandbox Code Playgroud)

这让我清理一些的caseif逻辑的node功能,使这一切有点整洁.

希望有所帮助,祝你好运.


I G*_*ERS 5

让我们来看看代码:

-module(ring).
-export([start/2, node/2]).
Run Code Online (Sandbox Code Playgroud)

这个名字node是我避免的,因为Erlang中的node()具有在某台机器上运行的Erlang VM的内涵 - 通常在几台机器上运行几个节点.我宁愿叫它ring_proc或类似的东西.

node(NodeNumber, NumberOfNodes) ->
   NextNodeNumber = (NodeNumber + 1) rem NumberOfNodes,
   NextNodeName = node_name(NextNodeNumber),
Run Code Online (Sandbox Code Playgroud)

这就是我们想要产生的东西,我们得到一个数字到下一个节点和下一个节点的名称.让我们看一下node_name/1插曲:

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

这个功能是个坏主意.您将需要一个需要作为原子的本地名称,因此您创建了一个可以创建任意此类名称的函数.这里的警告是原子表不是垃圾收集和限制,所以我们应该尽可能避免它.解决这个问题的诀窍是改变pids并反向构建环.最后的过程将结束戒指的结:

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).
Run Code Online (Sandbox Code Playgroud)

然后我们可以重写你的启动功能:

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

然后,环代码就是这样的:

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.
Run Code Online (Sandbox Code Playgroud)

并在环上点燃一些东西N次:

ring_ping(0, _Next) -> ok;
ring_ping(N, Next) ->
  Next ! ping
  receive
    ping ->
      ring_ping(N-1, Next)
  end.
Run Code Online (Sandbox Code Playgroud)

(这些代码都没有经过测试,所以它可能非常错误).

至于你的其余代码:

receive
  CircuitNumber ->
    io:format("Node ~p Circuit ~p~n", [NodeNumber, CircuitNumber]),
Run Code Online (Sandbox Code Playgroud)

CircuitNumber用一些原子标记:{run, CN}.

  LastNode = NodeNumber =:= NumberOfNodes - 1,
  NextCircuitNumber = case LastNode of
                       true ->
                         CircuitNumber - 1;
                       false ->
                         CircuitNumber
                     end,
Run Code Online (Sandbox Code Playgroud)

这可以使用if:

  NextCN = if NodeNumber =:= NumberOfNodes - 1 -> CN -1;
              NodeNumber =/= NumberOfNodes - 1 -> CN
           end,
Run Code Online (Sandbox Code Playgroud)

下一部分在这里:

  if
    NextCircuitNumber > 0 ->
      NextNodeName ! NextCircuitNumber;
    true ->
      ok
  end,
  if
    CircuitNumber > 1 ->
      node(NodeNumber, NumberOfNodes);
    true ->
      ok
  end
Run Code Online (Sandbox Code Playgroud)

确实需要这种true情况,除非你从未打过它.如果没有任何匹配,该过程将崩溃if.通常可以重新编写代码,以便不依赖于计算结构,例如上面的矿井提示代码.


使用此代码可以避免一些问题.当前代码的一个问题是,如果环中的某些东西崩溃,它就会被破坏.我们可以使用spawn_link而不是spawn将环连接在一起,因此这样的错误会破坏整个环.此外,ring_ping如果在环运行时发送消息,我们的功能将崩溃.这可以减轻,最简单的方法可能是改变环过程的状态,使其知道它正在运行并折叠ring_pingring.最后,我们可能还应该链接最初的spawn,所以我们最终没有一个大的响铃,但是没有人引用它.也许我们可以注册初始过程,以便以后轻松抓住戒指.

这个start功能在两个方面也很糟糕.首先,我们应该使用make_ref()标记唯一的消息并接收标记,因此另一个过程不能是险恶的,只是done在环工作时发送到启动过程.我们应该在环上添加一个监视器,同时它正在工作.否则我们将永远不会得到通知,在我们等待done消息(带标签)时应该响铃.顺便说一下,OTP在同步调用中都做了.


最后,最后:不,你不必清理注册.