【问题标题】:Why random order为什么随机排序
【发布时间】:2016-07-13 01:22:09
【问题描述】:

我正在关注模拟并行地图的模块。

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 } -> 中删除 pin 打印操作符时,我就没有正确顺序的列表了:

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 256, 225, 289, 361...]

为什么 pin 操作员会影响订单?

【问题讨论】:

    标签: elixir


    【解决方案1】:

    您正在启动一千个并发进程。它们中的每一个在完成时都会发送一条包含其 PID 和结果的消息。调度程序是非确定性的,因此可能会以随机顺序接收消息。

    pin 运算符的意思是“不分配变量,但模式匹配”。

    让我们考虑一个例子,当您有三个消息以相反的顺序出现时:

    {pid3, 9}
    {pid2, 4}
    {pid1, 1}
    

    {^pid, result} 匹配的是具体的 PID,所以当第一条消息到达时,模式匹配失败,消息被存储在邮箱中。

    当第二个出现时,也会发生同样的情况。

    当第三条消息到来时,它匹配,你得到结果,然后你继续匹配下一个 pid2,它已经在邮箱中。最后,您在 pid3 上进行匹配,并直接从邮箱中获取。

    使用{pid, result},您正在重新分配pid 变量。当第一条消息到达时,它会匹配并且 pid 将被分配pid3 的值。

    最后,您将获得按到达顺序排列的消息列表。

    另外,请参阅我关于 pin 运算符的其他答案:https://stackoverflow.com/a/27975233/912225

    【讨论】:

      【解决方案2】:

      当您映射元素集合并为集合中的每个元素生成一个新进程时,您将返回一个 pid 列表:这些 pid 将与您正在映射的集合的顺序相同,即,给定elem 的pid 在pid 列表中的位置与原始集合中的elem 相同。这就是映射的工作原理,您将操作应用于列表的每个元素并返回这些操作的结果列表。

      现在,您映射 pid 列表。当您在^pid 上匹配时,代码将阻塞,直到来自您正在映射的当前pid 的消息到达当前进程。但是,{^pid, result} 消息可能不是当前进程的消息队列中的唯一一个或第一个消息:由于所有生成的进程现在都在并行运行,因此它们不会按照它们的顺序发送结果已经产生了。这意味着当您在{^pid, result} 上接收和匹配时,消息队列可能在匹配{^pid, result} 的消息之前有其他消息({pid_1, result_1}{pid_2, result_2})。由于接收进程在 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(即,您传递给Enum.map/2 的函数中的pidpid1)。

      当你做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)。这意味着在结果列表中,第三个元素的结果(为此生成了pid3)代替了第一个元素的结果:随机顺序!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-07-27
        • 2020-10-09
        • 1970-01-01
        • 1970-01-01
        • 2022-01-25
        • 1970-01-01
        • 2012-12-04
        • 1970-01-01
        相关资源
        最近更新 更多