为什么随机顺序

时间:2016-03-25 09:32:06

标签: elixir

我跟随模块,模拟平行地图。

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...]

为什么针脚操作符会影响订单?

2 个答案:

答案 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正是由于我上面谈到的原因(产生的进程将以不可预测的顺序发回结果)。

更直观的表示:假设您在当前进程中有这个消息队列,在生成的进程开始运行之后(我们将调用生成的进程pid1pid2,等等) :

# 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/2pid1

当您执行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块将与之一起执行(即使我们映射的pidpid1)。这意味着在结果列表中,第3个元素(为其生成pid3)的结果取代了第1个元素的结果:随机顺序!