我正在使用Erlang进行并行计算训练。我有以下代码,用来计算圆周率:
start(To)->
Self = self(),
Cores = erlang:system_info(schedulers_online),
{Step, Delta} = {1/To, round(To / Cores)},
Pids = lists:map(fun (El) ->
spawn(fun()->
Self ! {sum, calculate(from(El, Delta), to(El, Delta), Step)}
end)
end, lists:seq(1, Cores)),
lists:sum([receive {sum, S} -> S end || _ <- Pids ]) * Step.
现在,它对我来说很神奇
lists:sum([receive {sum, S} -> S end || _ <- Pids ])
请解释一下这种魔法是如何运作的?
lists:sum([receive {sum, S} -> S end || _ <- Pids ])
让我们一部分接一部分,首先是函数sum,它只是对列表中的所有元素求和,即:
1> lists:sum([1, 2, 3]).
6
lists:sum(SomeList) * Step.
将在SomeList的元素和步骤中的值之间进行乘法运算。
有趣的部分是列表是如何构建的,它是由以下代码构建的:
[receive {sum, S} -> S end || _ <- Pids ]
你所拥有的是列表理解。Pids
是一个Erlang列表,它包含您创建的每个进程的进程ID(PID),用于处理上的pi数
Pids = lists:map(fun (El) ->
spawn(fun()->
Self ! {sum, calculate(from(El, Delta), to(El, Delta), Step)}
end)
end, lists:seq(1, Cores)).
假设你有4个核心,这个函数将创建4个进程,可能有一个像[<0.36.0>, <0.38.0>, <0.40.0>, <0.42.0>]
这样的列表,最重要的想法是,如果你有四个核心,你将创建四个进程,如果你拥有八个核心,八个进程,等等
每个进程都将调用函数calculate
,并将结果作为消息发送到Self
,您可以在这里看到:
Self ! {sum, calculate(from(El, Delta), to(El, Delta), Step)}
因此,如果你有4个元素,你可以尝试这样做:
6> [X || X <- [1, 2, 3, 4]].
[1,2,3,4]
在那里,您将使用列表中的每个元素构建一个列表压缩。现在,假设您不关心用于构建新列表的列表元素,您可以执行以下操作:
7> [1 || _ <- [1, 2, 3, 4]].
[1,1,1,1]
您将使用列表压缩作为bucle,您并不真正关心用于构建新列表的列表元素,正是在这种情况下,您正在执行以下操作:
[1 || _ <- Pids ]
您并不真正关心Pids
的内容,而是对列表Pids
的元素数量感兴趣。
因此,如果列表Pids有4个元素,您将执行四次接收功能:
receive
{sum, S} ->
S
end
该接收函数将侦听发送到进程的任何消息,其模式为{sum, S}
,与您使用{sum, calculate(from(El, Delta), to(El, Delta), Step)}
发送的模式相同。函数的返回值为S
,在这种情况下,它将是calculate(from(El, Delta), to(El, Delta), Step)
的结果。
最后,您将得到一个包含4个元素的列表(同样,假设您有4个核心),其结果由您开始的四个过程计算得出。