Concurrency Erlang课程并发性练习:我的答案可以改进吗?
我从以下位置进行此练习: 2) 编写一个启动N的函数 在环中处理,并发送 消息在所有的时间里都有M次 环中的进程。之后 已向进程发送消息 应该优雅地结束 以下是我的想法: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(
-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结束