【问题标题】:Getting result of a spawned function in Erlang在 Erlang 中获取衍生函数的结果
【发布时间】:2013-12-18 20:21:27
【问题描述】:

我目前的目标是编写计算 N 个元素列表的 Erlang 代码,其中每个元素都是其“索引”的阶乘(因此,对于 N = 10,我想得到 [1!, 2!, 3!,...,10!])。更重要的是,我希望每个元素都在一个单独的过程中计算(我知道它效率很低,但我希望稍后实现它并与其他方法比较它的效率)。

在我的代码中,我想使用一个函数作为给定 N 的“循环”,对于 N、N-1、N-2... 生成一个计算阶乘(N)并将结果发送到一些“收集”功能,它将接收到的结果打包到一个列表中。我知道我的概念可能过于复杂,所以希望代码能解释得更多:

messageFactorial(N, listPID) ->
    listPID ! factorial(N).      %% send calculated factorial to "collector".

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
nProcessesFactorialList(-1) ->
    ok;
nProcessesFactorialList(N) ->
    spawn(pFactorial, messageFactorial, [N, listPID]),   %%for each N spawn...
    nProcessesFactorialList(N-1).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
listPrepare(List) ->            %% "collector", for the last factorial returns
    receive                     %% a list of factorials (1! = 1).
        1 -> List;
        X ->
            listPrepare([X | List])
    end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
startProcessesFactorialList(N) ->
    register(listPID, spawn(pFactorial, listPrepare, [[]])),
    nProcessesFactorialList(N).

我想它会起作用,我的意思是 listPrepare 最终返回一个阶乘列表。但问题是,我不知道如何获取该列表,如何获取它返回的内容?至于现在我的代码返回正常,因为这是 nProcessesFactorialList 在完成时返回的内容。我考虑过最终将 listPrepare 中的结果列表发送到 nProcessesFactorialList,但它也需要是一个注册进程,我不知道如何从中恢复该列表。

所以基本上,如何从运行 listPrepare 的注册进程(这是我的阶乘列表)中获取结果?如果我的代码根本不正确,我会寻求如何改进它的建议。提前致谢。

【问题讨论】:

    标签: list concurrency parallel-processing functional-programming erlang


    【解决方案1】:

    第一个问题是您的listPrepare 进程对结果没有任何作用。最后尝试打印。

    第二个问题是您不需要等待所有进程完成,而是等待发送1 的进程,这是计算最快的阶乘。因此,在计算更复杂的数据之前肯定会收到此消息,而您最终只会得到几个响应。

    我在这里回答了一个与许多进程并行工作的类似问题:Create list across many processes in Erlang 也许那个会帮助你。

    【讨论】:

    • 每个流程只有在完成了他们应该做的所有事情后才会完成。第一个要计算的阶乘是 N!,因为 nProcessFactorialList 从 N 变为 -1。计算 1 的过程作为最后一个启动,当我在将结果附加到列表之前打印结果时,它们是正确的且顺序正确。如果出现问题,我会发送带有一些参数的每个信息来对传入的消息进行排序。问题不是打印列表,而是最终返回。
    • 如果您不相信我,请尝试一些至少需要几秒钟才能计算出来的大数字。
    • 3000 一切正常。我想知道,这是否会是一个问题,但在我看来,当我们从最大的数字计算时,当我们往下走一步时(所以计算 (n-1)!)我们已经计算出最大的乘数(n * (n-1)) 用于先前调用的过程。不过,如果您提到的任何错误发生,我只会将 {Result, Date} 发送给收集器,以便将答案按正确顺序打包到列表中,谢谢。
    • 我在回答中添加了一些 cmets。
    【解决方案2】:

    我建议你这个解决方案:

    -export([launch/1,fact/2]).
    
    launch(N) ->
        launch(N,N).
    
    %   launch(Current,Total)
    %   when all processes are launched go to the result collect phase 
    launch(-1,N) -> collect(N+1);
    launch(I,N) ->
    %   fact will be executed in a new process, so the normal way to get the answer is by message passing
    %   need to give the current process pid to get the answer back from the spawned process 
        spawn(?MODULE,fact,[I,self()]),
    %   loop until all processes are launched
        launch(I-1,N).
    
    % simply send the result to Pid.
    fact(N,Pid) -> Pid ! {N,fact_1(N,1)}.
    
    fact_1(I,R) when I < 2 -> R;
    fact_1(I,R) -> fact_1(I-1,R*I).
    
    % init the collect phase with an empty result list
    collect(N) -> collect(N,[]).
    
    % collect(Remaining_result_to_collect,Result_list)
    collect(0,L) -> L;
    % accumulate the results in L and loop until all messages are received
    collect(N,L) ->
        receive
            R -> collect(N-1,[R|L])
        end. 
    

    但更直接(单一进程)的解决方案可能是:

    1> F = fun(N) -> lists:foldl(fun(I,[{X,R}|Q]) -> [{I,R*I},{X,R}|Q] end, [{0,1}], lists:seq(1,N)) end. 
    #Fun<erl_eval.6.80484245>
    2> F(6).
    [{6,720},{5,120},{4,24},{3,6},{2,2},{1,1},{0,1}]
    

    [编辑]

    在多核、缓存和多任务底层系统的系统上,执行顺序是绝对不能保证的,消息发送也是如此。唯一的保证是在消息队列中,您知道您将根据消息接收的顺序分析消息。所以我同意 Dmitry 的观点,你的停止条件不是 100% 有效的。

    此外,使用startProcessesFactorialList,您生成listPrepare,它有效地收集所有阶乘值(1 除外!)然后在过程结束时简单地忘记结果,我猜这段代码 sn-p 不是正是您用于测试的那个。

    【讨论】:

      【解决方案3】:

      我如何完成这类任务的方式是

      -module(par_fact).
      
      -export([calc/1]).
      
      fact(X) -> fact(X, 1).
      
      fact(0, R) -> R;
      fact(X, R) when X > 0 -> fact(X-1, R*X).
      
      calc(N) ->
          Self = self(),
          Pids = [ spawn_link(fun() -> Self ! {self(), {X, fact(X)}} end)
                  || X <- lists:seq(1, N) ],
          [ receive {Pid, R} -> R end || Pid <- Pids ].
      

      结果:

      > par_fact:calc(25).
      [{1,1},
       {2,2},
       {3,6},
       {4,24},
       {5,120},
       {6,720},
       {7,5040},
       {8,40320},
       {9,362880},
       {10,3628800},
       {11,39916800},
       {12,479001600},
       {13,6227020800},
       {14,87178291200},
       {15,1307674368000},
       {16,20922789888000},
       {17,355687428096000},
       {18,6402373705728000},
       {19,121645100408832000},
       {20,2432902008176640000},
       {21,51090942171709440000},
       {22,1124000727777607680000},
       {23,25852016738884976640000},
       {24,620448401733239439360000},
       {25,15511210043330985984000000}]
      

      【讨论】:

      • 确实是一些非常干净的代码。可能听起来很蹩脚,但我从来没有想过将结果实际发送到产卵过程,现在我做到了!
      • @3yakuya:后来我在演示中使用了这个代码。仅仅十行代码就包含了整个 Erlang 语言的 80%。有单个赋值、递归、模式匹配、表达式、列表推导、生成、链接、发送、接收、本地和外部函数调用、元组、列表、模块和函数声明、多个函数子句、守卫、fun、BIF。还剩最少。
      猜你喜欢
      • 2021-02-24
      • 2021-07-27
      • 2012-06-17
      • 2012-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-27
      • 1970-01-01
      相关资源
      最近更新 更多