Erlang 环形基准测试程序

来源:互联网 发布:js 不等于 编辑:程序博客网 时间:2024/06/08 13:13

     题目来源于《Erlang 程序设计》(人民邮电出版社 ISBN 978-7-115-18869-4)第 115 页
的习题二。题目是在一个环中创建 N 个进程,然后沿着环发送一条消息 M 遍,共 N*M 遍,
统计消耗时间。
     题目其实有某些不太清楚的地方,在于发送消息的方式。有以下理解方式:
     1)进程1向进程2发送消息,进程2收到后再向进程3发送消息,此外的时间进程2
处于等待状态,其余进程依次类推;
     2)上一进程直接向下一进程发送 M 次消息,别的一概不管,也不等待;
     其实 1)中的理解有个小问题:谁来启动这个发送过程呢?大家都在等待啊。(当然,
可以采用一个额外的函数执行这一过程)好了,闲话不多说,我的个人理解是第二种,就此
开始。
     首先需要考虑的问题是怎样得到下一进程的 Pid,要知道,根据 107 页上的例子,可以
通过生成一个包含 N 个 Pid 的列表来得到 N 个进程。那么怎样知道我的下一个进程的 Pid
是多少呢?不知道的话怎么给人家发送信息啊。我的解决办法是将这个 Pid 列表传递给进程,
让进程找到自己的下一个进程的 Pid。
     好,这样一来引入一个新的问题,怎样得到下一进程的 Pid?我们需要一个判断自己的
Pid 在进程列表中的位置的函数。我的实现如下:
     %%判断 Element 在 L 中的位置的函数
     judge(Element,[_H|T]) when Element=:=_H ->     length(T);
     judge(Element,[_H|T]) ->                       judge(Element,T).
     需要说明的是,我返回的不是 Element 在列表中的位置,而是列表从 Element 以后尾
部(Tail)的长度。我们当然可以使 judge/2 这个函数直接返回 Element 在列表中的位置,
只需要将函数的第一行的 length(T)改为 length([_H|T])-length(T)即可,但是这样会增加一
次 length 这个 BIF 的调用。一次的增大倒是问题不大,可是对于上万上十万的进程,还是
值得考虑的。既然咱们是测试运行时间的,就该避免这样的行为,这叫消除误差。
     由 judge/2 返回尾部的长度,而 L 的长度为 N 是已知的,咱们只需要把 N 传递给每个
进程就好了,如下:
    %%将 Pid 列表传递给每个进程
    lists:foreach(fun(Pid) -> Pid!{Lew,N,M} end,L),
    这里的 Lew 是:
    Lew=lists:append(L,[lists:nth(1,L)]),
   将 l 的第一个元素加在 L 后面构成新的 Pid 列表 Lew,再发送给每个进程,这是让进程
成为环形的技巧。如下图所示:
    进程1            进程2                 进程3     ●●●    进程 N
                                   列表 L 的结构
   进程1          进程2            进程3         ●●●   进程 N  进程1
                                  列表 Lew 的结构
   由此,每个进程只需要向列表 Lew 的下一进程发送消息即可实现环形发送消息。
   接下来的问题是怎样发送消息 M 遍。再次利用 lists 模块中的 foreach 函数,用 lists 中
的 duplicate 函数构造一个包含 M 个 NextPid 的列表,然后利用 foreach 函数向下一进程
发送 M 次消息。实现如下:

           %%发送消息 M 次
     lists:foreach(fun(Pid) -> Pid!{hello} end,lists:duplicate(M,NextPid)),
     至此,主要的问题完全解决,完整代码附在最后。
     最后一个有意思的小问题,是关于每个进程退出的问题。如果采取向下一进程发送消息
{die}来使之退出(就像注释中写的那样),可能会出现本进程被上一进程结束而来不及向下
一进程发送退出信息,导致下一进程幸存下来。如果将%%NextPid!{die}, 一句恢复而将主
函数中倒数第二句注释掉,就可以测试下。我测试的结果是每次总有两个进程能存活下来。
运行 shell 的时候用 pman:start()命令打开进程管理器,就能看到上万的进程像洪水一样消
长的壮观景象,同时最后也能看到残留下来的两个进程。
      -module(ring_test).
-compile(export_all).

judge(Element,[_H|T]) when Element=:=_H ->                             %%判断Element在L中的位置的函数
      length(T);
judge(Element,[_H|T]) -> 
      judge(Element,T).

for(Num,Num,F) ->[F()];                                                         %%类似for循环的函数
for(I,Num,F) ->[F()|for(I+1,Num,F)].

subfun()->                                                                      %%收发信息的子函数
       
         receive
                        {List,N,M} ->
                                NextPid=lists:nth(N+2-judge(self(),List),List),
                    lists:foreach(fun(Pid) -> Pid!{hello} end,lists:duplicate(M,NextPid)),
                %%发送消息M次
                           
                                %%NextPid!{die},                                    %%若如此则可能出现幸存下的进程
                           
                                subfun();
            {hello} ->
                %io:format("ok,I got it!~n"),
                subfun();       
            {die} ->
                void                       
         end.

main_fun(N,M) ->
    L=for(1,N,fun()  -> spawn(ring_test,subfun,[]) end),
        Lew=lists:append(L,[lists:nth(1,L)]),                                   %%将L的第一个元素加在L后成Lew

        lists:foreach(fun(Pid) -> Pid!{Lew,N,M} end,L),                         %%
    lists:foreach(fun(Pid)  -> Pid!{die} end, L).                           %%向每个进程发出退出信号
time(N,M) ->
         {Rtime,_}=timer:tc(judge,main_fun,[N,M]),
     io:format("The realtime is ~p seconds~n",[Rtime/1000000]).

原创粉丝点击