Пройдемся по коду:
-module(ring).
-export([start/2, node/2]).
Имя node
я избегаю, потому что узел () в Erlang имеет коннотацию 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]))).
Эта функция плохая идея. Вам понадобится локальное имя, которое должно быть атомом, поэтому вы создали функцию, которая может создавать произвольные такие имена. Предупреждение здесь состоит в том, что таблица атомов не является сборщиком мусора и не ограничена, поэтому мы должны избегать этого, если это возможно. Хитрость для решения этой проблемы состоит в том, чтобы вместо этого пройти пиды и построить кольцо в обратном порядке. Окончательный процесс свяжет узел кольца:
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,
Это можно сделать с помощью if:
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
, если только вы никогда его не ударили. Процесс завершится сбоем, если в if
ничего не найдено. Часто можно переписать код так, чтобы он не слишком полагался на счетные конструкции, как в приведенном выше коде моих подсказок.
С этим кодом можно избежать некоторых проблем. Одна проблема с текущим кодом состоит в том, что, если что-то сломается в кольце, оно сломается. Мы можем использовать spawn_link
вместо spawn
, чтобы связать кольцо, поэтому такие ошибки уничтожат все кольцо. Кроме того, наша функция ring_ping
будет аварийно завершать работу, если ей будет отправлено сообщение во время работы звонка. Это может быть облегчено, самый простой способ, вероятно, состоит в том, чтобы изменить состояние кольцевого процесса так, чтобы он знал, что он в данный момент работает, и сложил ring_ping
в ring
. Наконец, мы, вероятно, должны также связать начальный спавн, чтобы у нас не было большого живого кольца, но ни у кого нет ссылки на него. Возможно, мы могли бы зарегистрировать начальный процесс, чтобы потом было легче схватить кольцо.
Функция start
также плоха в двух отношениях. Во-первых, мы должны использовать make_ref()
, чтобы пометить уникальное сообщение и получить его, так что другой процесс не может быть зловещим и просто отправить done
процессу запуска, пока работает кольцо. Вероятно, мы должны также добавить монитор на ринге, пока он работает. В противном случае мы никогда не будем проинформированы, должен произойти сбой звонка, пока мы ожидаем сообщение done
(с тегом). Кстати, OTP выполняет оба синхронных вызова.
Наконец, наконец: нет, вам не нужно очищать регистрацию.