我跟随模块,模拟平行地图。
defmodule Parallel do
def pmap(collection, fun) do
me = self
collection
|> Enum.map(fn (elem) ->
spawn_link fn -> send(me, { self, fun.(elem) }) end
end)
|> Enum.map(fn (pid) ->
receive do { ^pid, result } ->
result
end
end)
end
end
我按预期编译,运行并获得结果:
iex(5)> Parallel.pmap 1..1000, &(&1 * &1)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256 ...]
当我从receive do { pid, result } ->
移除针脚打印操作符时,我没有按正确的顺序删除列表:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 256, 225, 289, 361...]
为什么针脚操作符会影响订单?
答案 0 :(得分:4)
您正在启动一千个并发进程。它们中的每一个在完成时都会发送一条由PID和结果组成的消息。调度程序是非确定性的,因此可能会以随机顺序接收消息。
引脚运算符表示“不分配变量,但模式匹配”。
让我们考虑一个例子,当你有三条消息按相反的顺序排列时:
{pid3, 9}
{pid2, 4}
{pid1, 1}
使用{^pid, result}
,您将匹配具体的PID,因此当第一条消息到达时,模式匹配将失败,并且消息将存储在邮箱中。
当第二个出现时,同样的事情发生了。
当第三条消息到来时,它匹配,您得到结果,然后继续匹配已在邮箱中的下一个pid2。最后,您将匹配pid3,并直接从邮箱中获取。
使用{pid, result}
,您将重新分配pid
变量。当第一条消息出现时,它将匹配,并且将为pid分配值pid3
。
最后,您将按照他们到达的顺序获得一条消息列表。
另外,请参阅我关于pin运算符的其他答案:https://stackoverflow.com/a/27975233/912225
答案 1 :(得分:3)
当您映射元素集合并为集合中的每个元素生成一个新进程时,您将返回一个pid列表:这些pids将与您映射的集合的顺序相同,也就是说,给定elem
的pid与原始集合中的elem
位于pid列表中的相同位置。这就是映射的工作原理,您将操作应用于列表的每个元素,并返回这些操作的结果列表。
现在,您将映射到pids列表。当您在^pid
上匹配时,代码将会阻塞,直到您正在映射的当前pid
的消息到达当前进程。但是,{^pid, result}
消息可能不是当前进程的消息队列中唯一的消息或第一个消息:由于所有生成的进程现在并行运行,因此它们不会按顺序发送结果它们已经产生了。这意味着当您在{^pid, result}
上收到并匹配时,邮件队列可能会在匹配{pid_1, result_1}
的邮件之前包含其他邮件({pid_2, result_2}
,{^pid, result}
)。感谢接收进程在Erlang中的工作方式,如果这些消息与receive
中的模式不匹配,则会跳过这些消息,直到其中一个匹配(或者我们一直在等待匹配的新模式)。
当您在{pid, result}
上匹配时,您说任何双元素元组都没问题:在这种情况下,pid
可能不是您当前映射的pid
正是由于我上面谈到的原因(产生的进程将以不可预测的顺序发回结果)。
更直观的表示:假设您在当前进程中有这个消息队列,在生成的进程开始运行之后(我们将调用生成的进程pid1
,pid2
,等等) :
# The one on top is the first in the message queue:
{pid3, res3}
{pid1, res1}
{pid4, res4}
{pid5, res5}
{pid2, res2}
并且假设您当前正在映射pid1
(即,您传递给pid
的函数中的Enum.map/2
为pid1
。
当您执行receive do {^pid, res} -> ...
(pid == pid1
)时,第一条消息将不匹配;因此,下一条消息将匹配,这一条将匹配。 {pid3, res3}
将被放回消息队列中,receive
将与{pid1, res1}
一起执行。消息队列如下所示:
# The one on top is the first in the message queue:
{pid3, res3}
{pid4, res4}
{pid5, res5}
{pid2, res2}
回到原始队列:现在,如果你匹配{pid, res}
(没有^
pin运算符),任何双元素元组都会匹配;具体而言,{pid3, res3}
将匹配,receive
块将与之一起执行(即使我们映射的pid
为pid1
)。这意味着在结果列表中,第3个元素(为其生成pid3
)的结果取代了第1个元素的结果:随机顺序!