带阻塞的Erlang共享队列/共享计数器(并发、消息传递)

带阻塞的Erlang共享队列/共享计数器(并发、消息传递),erlang,Erlang,我有多个线程需要访问共享的数字列表。在下一个线程请求新编号之前,应该更新该列表。因此,我非常喜欢下面使用阻塞邮箱队列的示例。我可以肯定,连续的线程会按照请求的顺序获得一个新的唯一编号 下面的例子来自 我改变了计数器功能,以便为自己的项目工作。我的累加器进程知道何时停止从列表/计数器中提取数字,因为accum/5功能中有一个防护装置。在返回之前,每个累加器进程都会精确获取T/P编号 -module(pi). -export([main/0, child/0, get_next/1, sequenc

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

下面的例子来自

我改变了计数器功能,以便为自己的项目工作。我的累加器进程知道何时停止从列表/计数器中提取数字,因为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.
TL;博士

若您只需要一个唯一的标识符,那个么使用一个引用或工作PID本身的列表。 如果您需要一个单调序列,那么序列持有者应该是启动工作者繁殖的人——发送异步消息的多个进程提供关于排序的零保证。 再深入一点

在我看来,最好是让序列持有者产生进程,而不是强迫任何东西接近单调序列,尤其是没有间隙的序列!超出异步消息传递进程

例如:

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.
您可以手动将其放入系统进程中执行陷阱退出等操作,或者将其设置为主管可能是最好的计划,但并非总是如此-我的意思是,除非必须,否则谁真的想编写另一个handle\u down/2函数?或者别的什么。但关键是计数器递增的方式与新生成的进程启动的方式相同——除非发生崩溃,否则这将实际上是一个原子操作

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

如果这只是一个您想要的计数,那么只需将生成的所有PID记录在列表中,然后运行LengthalpID就可以给出您的答案。如果序列计数并按顺序进行这一事实很重要,那么除非通过使用纯粹的同步消息传递使序列保持过程成为整个系统的瓶颈,否则您无法知道什么进程在何时得到了什么数字,因为我们无法知道消息的发送顺序、接收顺序,以及相对于其他工人发送和接收的订单响应


因此,直接切入主题,让工作人员跟踪序列本身的过程就可以消除排序问题。请记住,序列持有者可能只是告诉另一个流程(如主管)生成工作人员,然后稍后将其序列号传递给他们,只要您有一个K/V记录,告诉您Pid属于哪个序列号。

加法是可交换的,祝您万岁!序列的顺序并不重要,只有完整性才重要。构建类似这样的结构的典型方法是让父进程持有列表,为每个项生成一个进程,为每个生成的进程接收一个结果,然后在该列表上执行其最终计算。我们可以继续聊天;评论是一个很难弄清事情真相的地方:我最终自己解决了这个问题,并按照作者的意图修改了共享计数器。@user2355058好听!:-当您更突然地使用Erlang时,一切都会立刻变得有意义,然后您会想知道您是如何想到用另一种方式编码某些东西的。它不是解决所有问题的最佳工具,但对于今天编写的大部分程序来说,它确实是。
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.