2011-02-27 62 views
5

我從erlang.org course做這個運動:Erlang課程併發練習:我的答案可以改進嗎?

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 

如果我其實知道二郎,將可能我做不同的解決這個代碼?具體如下:

  • 是否有任何其他的方法來指定最後兩個if語句中的無效「true」子句?

  • 我的確確實終止了嗎?結束已註冊的流程時是否需要採取特殊行動?

回答

6

歡迎來到二郎!我希望你和我一樣喜歡它。

在最後兩個if語句中指定無效「真」子句還有其他選擇嗎?

您可以將它們關閉。我用這個運行你的代碼:

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

它適用於我。

我確實終止了優雅嗎?結束已註冊的流程時是否需要採取特殊行動?

是的,你是。您可以通過運行i().命令來驗證這一點。這將向您顯示進程列表,如果您的註冊進程沒有終止,您會看到很多已註冊的進程,例如node0node1等。您也無法再次運行您的程序,因爲嘗試註冊一個已經註冊的名字會出錯。

至於其他事情你可以做的改善代碼,沒有太多,因爲你的代碼基本上是好的。我可以做的一件事是取消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. 

,讓我清理一些caseif邏輯在你的node功能,使這一切有點整潔。

希望有所幫助,祝你好運。

+0

好評論這裏! – 2011-02-28 01:42:34

5

讓遍歷代碼:

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

名稱node是一個我避免了因爲在二郎一個節點()有一些機器上運行一個Erlang VM的內涵 - 通常是幾個節點上幾臺機器上運行。我寧願將它稱爲ring_proc或其他類似的東西。

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

這就是我們試圖產生的結果,我們得到一個數字到下一個節點和下一個節點的名稱。讓我們看一下node_name/1作爲一個插曲:

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

這個函數是一個壞主意。您將需要一個需要成爲原子的本地名稱,因此您創建了一個可以創建任意這種名稱的函數。這裏的警告是原子表沒有垃圾收集和限制,所以我們應該儘可能避免。解決這個問題的訣竅就是傳遞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}

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

接下來這兒的一部分:

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 

確實需要的情況下true,除非你永遠不打它。如果if中沒有任何匹配,則該過程將崩潰。通常可以將代碼重新佈線,以免在計數結構上做太多的工作,就像上面的代碼一樣。


這段代碼可以避免幾個麻煩。當前代碼的一個問題是,如果環中的某個東西崩潰,它就會被破壞。我們可以使用spawn_link而不是spawn將環鏈接在一起,因此這樣的錯誤將會破壞整個環。此外,我們的ring_ping函數會在環運行時發送消息時崩潰。這可以得到緩解,最簡單的方法可能是改變環進程的狀態,以便知道它正在運行並將ring_ping折成ring。最後,我們可能還應該鏈接最初的產卵,這樣我們就不會產生一個活的大環,但沒有人可以參考。也許我們可以註冊最初的過程,以便稍後抓住環。

start功能在兩個方面也不好。首先,我們應該使用make_ref()來標記一個唯一的消息,並接收標籤,所以另一個過程不會是邪惡的,只需在啓動過程中將done發送到環中即可。我們也許應該在環上增加一個監視器,它在工作。否則,我們永遠不會被通知,應該在我們等待done消息(帶標籤)時響鈴。順便說一下,OTP在同步調用中都會執行。


最後,最後:不,你不必清理註冊。

3

我的同事們提出了一些優秀的觀點。我還想提一下,通過註冊流程而不是實際創建一個環來避免問題的初始意圖。以下是一種可能的解決方案:

-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. 

此解決方案使用記錄。如果你不熟悉它們,請閱讀關於它們的所有內容here

每個節點由loop/2函數定義。 loop/2的第一個條款涉及創建環(構建階段),第二個條款涉及打印消息(數據階段)。請注意,所有子句以loop/2的呼叫結束,但rounds=0子句除外,這表示該節點已完成其任務並應死亡。這就是優美的終止。還要注意用來告訴節點它正處於構建階段的破解 - NextNode不是一個pid,而是一個整數。