Elixir 按指定顺序累积消息输出

Elixir 按指定顺序累积消息输出,elixir,Elixir,我希望以指定的顺序累积不同进程发送的消息的输出 例如,我有一个PID列表[pid0,pid1]。如果我先得到pid0,那么就可以了。如果我首先得到pid1,那么在得到pid0之前不会发生任何事情 我知道这可以通过使用地图或关键字查找和在收到所有消息后排序来解决。然而,这可以通过模式匹配来实现吗 例如,编写这样的内容以确保它只尝试接收消息input\u pid(列表中的第一条消息): 让我们先看一个不起作用的示例,然后找到修复它的方法: caller = self pids = for x &l

我希望以指定的顺序累积不同进程发送的消息的输出

例如,我有一个PID列表
[pid0,pid1]
。如果我先得到
pid0
,那么就可以了。如果我首先得到
pid1
,那么在得到
pid0
之前不会发生任何事情

我知道这可以通过使用地图或关键字查找和在收到所有消息后排序来解决。然而,这可以通过模式匹配来实现吗

例如,编写这样的内容以确保它只尝试接收消息
input\u pid
(列表中的第一条消息):


让我们先看一个不起作用的示例,然后找到修复它的方法:

caller = self

pids = for x <- 1..100 do
  spawn fn ->
    :random.seed(:erlang.monotonic_time)
    :timer.sleep(:random.uniform(1000))
    send(caller, x)
  end
end

for _ <- pids do
  receive do
    x -> IO.inspect(x)
  end
end
请注意,这可能会淹没调用方的进程收件箱。在最坏的情况下,首先生成的进程可能最后接收,然后所有其他消息将一直保留到最后

现在,您是否需要更进一步取决于流程的数量,您应该为您的具体案例测试结果。大量进程的问题在于邮箱的处理方式:每次调用
receive
,它都会遍历所有邮件,直到找到匹配项为止。这意味着在最坏的情况下,您将迭代邮箱
n+(n-1)+(n-2)+…+1=n*(n+1)/2次(二次时间复杂度
O(n²)
),这可能成为瓶颈

在这种情况下,更好的选择可能是立即接收消息,将它们存储在地图中,然后按照正确的顺序读取它们。这样做的缺点是,您必须一直等到收到所有消息。在第一种方法中,消息在下一个消息到达时立即被处理。下面是一个如何做到这一点的示例。在本例中,我使用了100000个进程,而另一种方法已经变得非常缓慢:

caller = self

pids = for x <- 1..100_000 do
  spawn fn ->
    :random.seed(:erlang.monotonic_time)
    :timer.sleep(:random.uniform(1000))
    send(caller, {self, x})
  end
end

results = Enum.reduce pids, %{}, fn(_, results) ->
  receive do
    {pid, x} -> Map.put(results, pid, x)
  end
end

for pid <- pids do
  IO.inspect(results[pid])
end
caller=self
pids=对于x
:random.seed(:erlang.monotonic_time)
:timer.sleep(:random.uniform(1000))
发送(调用方,{self,x})
结束
结束
结果=Enum.reduce PID,%{},fn(u,results)->
接收do
{pid,x}->Map.put(结果,pid,x)
结束
结束

对于pid@patrick oscity给出了一个非常深入的答案,我非常喜欢。然而,我已经使用了一个版本,它可以通过对pin操作符进行模式匹配来实现这一点

在上下文中,我正在编写一个累加器,当接收到所有输入时,它将所有输入的结果转发给执行器。我还希望累积输出(一个列表)遵守最初发送其输出的进程的预定义顺序

完成此操作后,累加器再次开始等待消息

累加器如下所示:

def start_link(actuator, actuator_pid) do
  Task.start_link(fn -> loop(actuator, actuator_pid, actuator.inputs, []) end)
end

defp loop(actuator, actuator_pid, [], acc) do
  result = acc |> Enum.reverse
  send actuator_pid, {:forward, self(), result}
  loop(actuator, actuator_pid, actuator.inputs, [])
end

defp loop(actuator, actuator_pid, [input | remaining_inputs], acc) do
  receive do
    {:forward, ^input, output} ->
      loop(actuator, actuator_pid, remaining_inputs, [output | acc])
  end
end
为了证明它符合我的要求,测试如下:

test "When I recieve the outputs from all my inputs, then I return the accumulated result in the order of my actuator's inputs" do

{_, pid0} = Task.start_link(fn -> test_loop end)
{_, pid1} = Task.start_link(fn -> test_loop end)
{_, pid2} = Task.start_link(fn -> test_loop end)

actuator = %Cerebrum.Actuator{
  inputs: [pid0, pid1, pid2]
}

actuator_pid = self()
{_, pid} = start_link(actuator, actuator_pid)

send pid, {:forward, pid0, 0}
refute_receive {:forward, pid, [0]}

send pid, {:forward, pid1, 1}
refute_receive {:forward, pid, [0, 1]}

send pid, {:forward, pid2, 2}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid0, 0}
send pid, {:forward, pid1, 1}
send pid, {:forward, pid2, 2}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid0, 0}
send pid, {:forward, pid2, 2}
send pid, {:forward, pid1, 1}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid1, 1}
send pid, {:forward, pid0, 0}
send pid, {:forward, pid2, 2}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid1, 1}
send pid, {:forward, pid2, 2}
send pid, {:forward, pid0, 0}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid2, 2}
send pid, {:forward, pid1, 1}
send pid, {:forward, pid0, 0}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid2, 2}
send pid, {:forward, pid1, 1}
send pid, {:forward, pid0, 0}
assert_receive {:forward, pid, [0, 1, 2]}

end

defp test_loop do
  test_loop
end

谢谢你的深入回答。我使用了另一个版本,使用了直接模式匹配。但因为它太有趣了,我会认为你的答案是正确的。我认为这基本上可以归结为我的第一种方法,从我的角度来看,似乎很好!
def start_link(actuator, actuator_pid) do
  Task.start_link(fn -> loop(actuator, actuator_pid, actuator.inputs, []) end)
end

defp loop(actuator, actuator_pid, [], acc) do
  result = acc |> Enum.reverse
  send actuator_pid, {:forward, self(), result}
  loop(actuator, actuator_pid, actuator.inputs, [])
end

defp loop(actuator, actuator_pid, [input | remaining_inputs], acc) do
  receive do
    {:forward, ^input, output} ->
      loop(actuator, actuator_pid, remaining_inputs, [output | acc])
  end
end
test "When I recieve the outputs from all my inputs, then I return the accumulated result in the order of my actuator's inputs" do

{_, pid0} = Task.start_link(fn -> test_loop end)
{_, pid1} = Task.start_link(fn -> test_loop end)
{_, pid2} = Task.start_link(fn -> test_loop end)

actuator = %Cerebrum.Actuator{
  inputs: [pid0, pid1, pid2]
}

actuator_pid = self()
{_, pid} = start_link(actuator, actuator_pid)

send pid, {:forward, pid0, 0}
refute_receive {:forward, pid, [0]}

send pid, {:forward, pid1, 1}
refute_receive {:forward, pid, [0, 1]}

send pid, {:forward, pid2, 2}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid0, 0}
send pid, {:forward, pid1, 1}
send pid, {:forward, pid2, 2}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid0, 0}
send pid, {:forward, pid2, 2}
send pid, {:forward, pid1, 1}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid1, 1}
send pid, {:forward, pid0, 0}
send pid, {:forward, pid2, 2}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid1, 1}
send pid, {:forward, pid2, 2}
send pid, {:forward, pid0, 0}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid2, 2}
send pid, {:forward, pid1, 1}
send pid, {:forward, pid0, 0}
assert_receive {:forward, pid, [0, 1, 2]}

send pid, {:forward, pid2, 2}
send pid, {:forward, pid1, 1}
send pid, {:forward, pid0, 0}
assert_receive {:forward, pid, [0, 1, 2]}

end

defp test_loop do
  test_loop
end