Elixir:同时启动流程

时间:2016-03-15 19:14:48

标签: elixir

我们说我有这个模块

defmodule Loader do

  def spawn_pools(0, host, iteations, pids) do
    launch!(pids) #something I want to achieve
  end

  def spawn_pools(pools, host, iterations, pids) do
    pid = spawn_link(__MODULE__, :siege, [host, iterations])
    spawn_pools(pools-1, host, iterations, [pid|pids])
  end

end

因此,如果其他模块将执行Loader.spawn_pools(10, host, iterations, []),它将产生10个执行方法围攻的过程。

问题在于我希望它尽可能并行 - 在同一时刻开始执行所有进程。

所以我想到了这个

def siege do
  receive do
   {:launch} -> #...
  end
end

但这有点让我遇到同样的问题 - 所以我需要同时向所有这些进程发送:launch。这让我接受了递归,这是另一层同样的问题。

P.S。我是Erlang / Elixir范例的新手,所以我可能会错过一些东西吗?

2 个答案:

答案 0 :(得分:3)

Erlang和Elixir在每个进程中执行代码顺序;由于流程是从其他流程产生的,因此产生的行为是顺序的的语言性质。没有办法同步≥1个进程的产生。向每个进程发送消息以同步"同步"过程的开始'作业有同样的问题:发送消息是顺序的,因此主进程仍将一次发送一条消息。即使你通过多个进程分发产卵/消息发送,保证它们都完全在同一时间开始基本上是不可能的。

但是,消息发送和进程生成都是非常快速的操作,因此问题通常很小。

解决方案可能是在生成任何进程之前获取当前时间戳,并将其传递给每个新进程:该进程将获取其当前时间戳,减去初始时间戳,从而得到"之后&#34 ;它已经产生了。您可以使用此信息来利用:timer.sleep/1之类的内容来尝试和模拟同步启动,但它仍然会受到时钟和其他因素的不同程度的精确度影响:)。

答案 1 :(得分:0)

您可以获得的最接近的是使用列表理解。它是一种语言结构,因此理论上可以编译为并行执行(但是,它不是由于后面描述的其他问题)。了解parallel_eval function如何在Erlang官方库中编写。这基本上是这样的:

[spawn(fun() -> ReplyTo ! {self(), promise_reply, M:F(A)} end) || A <- ArgL]

您可以在my Erlang code中看到的示例。

如果你考虑它,就不可能完全并行地开始执行某些进程,因为在最低级别,物理CPU必须按顺序开始执行每个进程。 Erlang VM需要为新进程分配一个堆栈,根据文档记录需要309 words内存。然后它需要传递初始参数,将其添加到调度程序等。另请参阅此thread which contains more technical references explaining Erlang processes

修改

您可以对创建一个进程所需的时间进行基准测试,这个简单的代码可以快速了解两个方法:

-module(spawner).

-export([start1/1, start2/1]).

start1(N) ->
    start_new1(erlang:monotonic_time(), self(), 4),
    loop(round(math:pow(4, N)), 0, []).

start_new1(Start, Pid, N) ->
    Fun = fun() -> child(Start, Pid, N-1) end,
    [spawn(Fun) || _ <- lists:seq(1, 4)].

child(Start, Pid, 0) -> send_diff(Start, Pid);
child(Start, Pid, N) -> start_new1(Start, Pid, N).

loop(All, All, Acc) ->
    {All, lists:sum(Acc)/All, lists:min(Acc), lists:max(Acc)};
loop(All, Y, Acc) ->
    receive Time -> loop(All, Y+1, [Time|Acc]) end.

send_diff(Start, Pid) ->
    Diff = erlang:monotonic_time() - Start,
    Pid ! erlang:convert_time_unit(Diff, native, micro_seconds).


start2(N) ->
    All = round(math:pow(4, N)),
    Pid = self(),
    Seq = lists:seq(1, All),
    Start = erlang:monotonic_time(),

    Fun = fun() -> send_diff(Start, Pid) end,
    [spawn(Fun) || _ <- Seq],
    loop(All, 0, []).

start1/1产生一个进程树 - 每个进程产生4个子进程。论证是代数,例如将有4^N个叶子进程(N=4为256个)。 start2/1产生相同有效数量的进程,但是依次逐个进行。在这两种情况下,输出是产生一个进程(树的情况下的叶子)的平均,最小和最大时间量,以微秒为单位。

1> c(spawner).
{ok,spawner}
2> spawner:start1(4).
{256,868.8671875,379,1182}
3> spawner:start2(4).
{256,3649.55859375,706,4829}
4> spawner:start2(5).
{1024,2260.6494140625,881,4529}

请注意,在start1除了叶子进程之外,还会有更多的支持进程只能生成子进程。似乎从第一种情况开始生成每个叶子孩子的时间较短,但在我的环境中,它并不想在N=5的合理时间内完成。但你可以采取这个想法或类似的东西,并根据你的需要调整每个进程产生的N和子进程数量。