Erlang共享队列/共享计数器与阻塞(并发,消息传递)

时间:2015-04-16 02:11:45

标签: erlang

我有多个线程需要访问共享的数字列表。应在下一个线程请求新号码之前更新该列表。因此,我真的很喜欢以下使用阻止邮箱队列的示例。我可以确定连续的线程按照请求的顺序获得一个新的唯一编号。

以下示例来自this site

sequence1.erl (raw implementation)
-module(sequence1).
-export([make_sequence/0, get_next/1, reset/1]).

% Create a new shared counter.
make_sequence() ->
  spawn(fun() -> sequence_loop(0) end).
sequence_loop(N) ->
  receive
  {From, get_next} ->
        From ! {self(), N},
        sequence_loop(N + 1);
  reset ->
        sequence_loop(0)
  end.
% Retrieve counter and increment.
get_next(Sequence) ->
  Sequence ! {self(), get_next},
  receive
        {Sequence, N} -> N
  end.
% Re-initialize counter to zero.
reset(Sequence) ->
Sequence ! reset.

我改变了计数器功能以适用于我自己的项目。我的累加器 - 进程知道何时停止从列表/计数器中取数,因为在accum / 5函数中有一个保护。每个累加器 - 过程在返回之前都会获取正确的T / P编号。

-module(pi).
-export([main/0, child/0, get_next/1, sequence_loop/1]).

%main/0
main() ->
T = 1000000,        %io:fread("Terms? ","~d"),
P = 4,              %io:fread("Processes? ","~d"),
pi(T,P).

 %  Code executed by the parent process.
pi(T, P) -> 
    Pid0 = spawn(pi, sequence_loop, [1]),
    Pid1 = spawn(pi, child, []),
    Pid2 = spawn(pi, child, []),
    Pid3 = spawn(pi, child, []),
    Pid4 = spawn(pi, child, []),

    Pid0 ! {start, 1},

   Tpart = 250000,          %Tpart = T div P,
   Width = 1.0 / T,
   Pid1 ! {work, self(), T, Tpart, Width, Pid0},
   Pid2 ! {work, self(), T, Tpart, Width, Pid0},
   Pid3 ! {work, self(), T, Tpart, Width, Pid0},
   Pid4 ! {work, self(), T, Tpart, Width, Pid0},
    await([Pid1, Pid2,Pid3, Pid4], 0.0).

%  Parent awaits replies from the child processes.
await([], Final) -> io:format(" Final Sum: ~.8f \n", [(Final * 4.0)]);
await([Pid | Rest], Final) ->
    receive
        {done, Pid, Sum} ->
        Partial = Final + Sum,
        await(Rest, Partial)
    end.

%  Code executed by the child processes. In pi, child calls the accum function which returns a partial sum. 
% Once the sum has been returned, child sends a done message to the parent which includes the partial sum from accum. The following code will compile and run.

child() ->
    receive
        {work, Parent, T, Tpart, Width, Pid0} ->
            Partial = accum(T, Tpart, Width, Pid0, 0, 1),
            Parent ! {done, self(), Partial}
    end.


    %calculate the area of rectangles. 
    accum(T, Tpart, Width, Pid0, Sum, Count) when Count > Tpart -> Sum; %base case says: work T / P times.
    accum(T, Tpart, Width, Pid0, Sum, Count) ->

            Temp = get_next(Pid0), %gets a number from the shared counter
            Temp0 = ((Temp - 0.5) * Width),
            Temp1 = math:pow(Temp0, 2.0),
            Temp2 = 1.0 - Temp1,
            Temp3 = math:sqrt(Temp2),
            Temp4 = Temp3 * Width,
            Partial = Sum + Temp4,
            Count2 = Count + 1,
            accum(T, Tpart, Width, Pid0, Partial, Count2). %recursive call



  %Shared Counter. Uses message passing and blocking to allow exclusive       access by one process at a time

   % Main Counter object
    sequence_loop(N) ->
    receive
    {start, G} -> 
    sequence_loop(G);
    {From, get_next} ->
    From ! {self(), N},
    sequence_loop(N + 1)
    end.

  % Retrieve counter and increment.
  get_next(Pid) ->
  Pid ! {self(), get_next},
  receive
 {Pid, N} -> N
  end.

1 个答案:

答案 0 :(得分:0)

<强> TL; DR

  • 如果您只需要一个唯一标识符,请使用引用或工作者自己的列表。
  • 如果您需要单调序列,那么序列持有者应该是启动工作人员生成的序列持有者 - 发送异步消息的多个进程提供保证有关排序。

更深入

IMO最好让序列持有者成为产生过程的过程,而不是试图强迫任何接近单调序列(特别是没有间隙的序列!)的异步消息传递过程。

例如:

seq_loop(N, Workers) ->
  receive
    {spawn, {M, F, A}} ->
        Worker = spawn_monitor(M, F, [N | A]),
        seq_loop(N + 1, [Worker | Workers]);
    Down = {'DOWN', _, process, _, _} ->
        NewWorkers = handle_down(Workers, Down),
        seq_loop(N, NewWorkers);
    Other ->
        whatever_else(),
        seq_loop(N, Workers)
  end.

您可以将其转换为系统流程(手动执行trap_exit等)或使其成为主管(可能是最佳计划,但并非总是如此 - 我的意思是,谁真的想写另一个handle_down/2函数,除非他们必须?)或其他什么。但重点是计数器增加的方式与新生成的进程启动方式相同 - 除非发生崩溃,否则这将是一个原子操作。

我提倡这种方法的原因是假设问题。在没有意识到的情况下,您可以很快开始对序列的性质做出假设 - 它不仅为您提供了序列被访问次数的计数,而且 >命令它给出的整数与系统中使用这些数字的顺序有任何关系。

如果它只是你想要的一个计数,那么只需跟踪你产生的所有PID,将它们放入一个列表中,然后运行length(AllPids)就可以得到你的答案。如果顺序计数并按顺序排列是重要的,那么除非你通过使用纯同步消息传递使序列保持过程成为整个系统的瓶颈,否则你无法知道什么过程得到了什么数字,因为我们无法知道邮件的发送顺序,收到的顺序,以及相对于其他工作人员发送和接收的订单响应

因此,只需切入追逐并让生成工人的过程跟踪序列本身就可以消除排序问题。请记住,序列持有者可能只是告诉另一个进程(如主管)产生工作者,然后稍后将它们的序列号传递给他们,只要你有一个K / V记录告诉你Pid属于什么序列号。