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

Concurrency Erlang课程并发性练习:我的答案可以改进吗?,concurrency,erlang,Concurrency,Erlang,我从以下位置进行此练习: 2) 编写一个启动N的函数 在环中处理,并发送 消息在所有的时间里都有M次 环中的进程。之后 已向进程发送消息 应该优雅地结束 以下是我的想法: -module(ring). -export([start/2, node/2]). node(NodeNumber, NumberOfNodes) -> NextNodeNumber = (NodeNumber + 1) rem NumberOfNodes, NextNodeName = node_name(

我从以下位置进行此练习:

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]))).
以下是它的输出:

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
如果我真的了解Erlang,我能做些不同的事情来改进这段代码吗?具体来说:

  • 除了在最后两个if语句中指定donothing“true”子句外,还有其他选择吗

  • 我真的优雅地结束了吗?在结束已注册的流程时是否需要任何特殊操作


    • 欢迎来到Erlang!我希望你和我一样喜欢它

      除了在最后两个if语句中指定donothing“true”子句外,还有其他选择吗

      你可以把这些关了。我用以下代码运行了您的代码:

      if NextCircuitNumber > 0 ->
        NextNodeName ! NextCircuitNumber
      end,
      if CircuitNumber > 1 ->
        node(NodeNumber, NumberOfNodes)
      end
      
      这对我很有效

      我真的优雅地结束了吗?在结束已注册的流程时是否需要任何特殊操作

      是的,你是。您可以通过运行
      i()。
      命令来验证这一点。这将向您显示进程列表,如果您注册的进程没有终止,您将看到许多剩余的注册进程,如
      node0
      node1
      ,等等。您也将无法再次运行程序,因为尝试注册已注册的名称时会出错

      至于您可以做些什么来改进代码,因为您的代码基本上很好,所以没有太多改进。我可能要做的一件事是去掉
      NextNodeName
      变量。您只需将消息直接发送到
      node\u name(NextNodeNumber)
      即可

      此外,您可能还可以进行更多的模式匹配来改进。例如,我在使用代码时所做的一个更改是通过传入最后一个节点的编号
      (NumberOfNodes-1)
      ,而不是传入
      NumberOfNodes
      ,来生成进程。然后,我可以像这样在
      节点/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.
      
      这使我能够清理
      节点
      函数中的一些
      案例
      if
      逻辑,并使其更加整洁


      希望对您有所帮助,祝您好运。

      让我们来看看代码:

      -module(ring).
      -export([start/2, node/2]).
      
      我避免使用名称
      node
      ,因为Erlang中的node()具有运行在某台机器上的Erlang VM的含义-通常是运行在多台机器上的多个节点。我宁愿称之为
      ring\u proc
      或类似的东西

      node(NodeNumber, NumberOfNodes) ->
         NextNodeNumber = (NodeNumber + 1) rem NumberOfNodes,
         NextNodeName = node_name(NextNodeNumber),
      
      这就是我们试图产生的,我们得到下一个节点的编号和下一个节点的名称。让我们把
      节点名称/1
      作为一个插曲:

      node_name(NodeNumber) ->
         list_to_atom(lists:flatten(io_lib:format("node~w", [NodeNumber]))).
      
      这个函数是个坏主意。您将需要一个本地名称,该名称必须是atom,因此您创建了一个可以创建任意此类名称的函数。这里的警告是,atom表不是垃圾收集和限制的,因此如果可能,我们应该避免使用它。解决这个问题的诀窍是通过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).
      
      然后我们可以重写你的启动函数:

      start(NumberOfNodes, NumberOfCircuits) ->
        RingStart = mk_ring(NumberOfNodes)
        RingStart ! {operate, NumberOfCircuits, self()},
        receive
          done ->
              RingStart ! stop
        end,
        ok.
      
      然后,环码是大致如下所示:

      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.
      
      在戒指周围发射东西N次:

      ring_ping(0, _Next) -> ok;
      ring_ping(N, Next) ->
        Next ! ping
        receive
          ping ->
            ring_ping(N-1, Next)
        end.
      
      (顺便说一下,这些代码都没有经过测试,所以很可能是完全错误的)

      至于代码的其余部分:

      receive
        CircuitNumber ->
          io:format("Node ~p Circuit ~p~n", [NodeNumber, CircuitNumber]),
      
      我会用一些原子标记
      CircuitNumber
      {run,CN}

        LastNode = NodeNumber =:= NumberOfNodes - 1,
        NextCircuitNumber = case LastNode of
                             true ->
                               CircuitNumber - 1;
                             false ->
                               CircuitNumber
                           end,
      
      这可以通过以下条件来实现:

        NextCN = if NodeNumber =:= NumberOfNodes - 1 -> CN -1;
                    NodeNumber =/= NumberOfNodes - 1 -> CN
                 end,
      
      下一部分:

        if
          NextCircuitNumber > 0 ->
            NextNodeName ! NextCircuitNumber;
          true ->
            ok
        end,
        if
          CircuitNumber > 1 ->
            node(NodeNumber, NumberOfNodes);
          true ->
            ok
        end
      
      确实需要
      true
      大小写,除非您从未点击它。如果
      中没有匹配项,则进程将崩溃。通常可以对代码进行重新布线,以避免过多地依赖于计数结构,就像上面我的代码提示一样


      使用此代码可以避免一些麻烦。当前代码的一个问题是,如果某个东西在环中崩溃,它就会被破坏。我们可以使用
      spawn\u link
      而不是
      spawn
      将环链接在一起,因此此类错误将破坏整个环。此外,如果在环运行时发送消息,我们的
      ring\u ping
      功能将崩溃。这是可以缓解的,最简单的方法可能是改变环进程的状态,使其知道当前正在运行,并将
      ring\u ping
      折叠到
      ring
      。最后,我们可能还应该链接最初的繁殖,这样我们就不会有一个大的环是活的,但没有人参考。也许我们可以注册最初的过程,以便以后很容易抓住戒指

      start
      功能在两方面也不好。首先,我们应该使用
      make_ref()
      标记一条唯一的消息并接收该标记,这样另一个进程就不会很危险,只需在环工作时将
      done
      发送到启动进程。我们可能还应该在环上添加一个监视器,而它正在工作。否则,我们将永远不会收到通知,在等待
      done
      消息(带标签)时,将发生响铃崩溃。顺便说一下,OTP在其同步调用中实现了这两种功能



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

      我的同事提出了一些非常好的观点。我还想提到,通过注册进程而不是实际创建环来避免问题的最初意图。以下是一种可能的解决方案:

      -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.
      
      此解决方案使用记录。如果你不熟悉它们,请阅读所有关于它们的信息

      每个节点由
      循环/2
      函数定义。
      loop/2
      的第一个子句处理创建环(构建阶段),第二个子句处理打印消息(数据阶段)。请注意,所有子句都以调用
      loop/2结束