我遇到了自动启动我的监督树的僵局问题。一台GenServer的初始状态是树中另一台主管的子级工作。这是代码:

主管和工人:

defmodule Parallel.Worker.Supervisor do
  import Supervisor.Spec

  def start_link do
    # Start up a worker for each core
    schedulers = :erlang.system_info(:schedulers)
    children = Enum.map(1..schedulers,
      &(worker(Parallel.Worker.Server, [], id: "Parallel.Worker#{&1}")))

    opts = [strategy: :one_for_one, name: Parallel.Worker.Supervisor]
    Supervisor.start_link(children, opts)
  end

  def workers do
     Process.whereis(Parallel.Supervisor)
      |> Supervisor.which_children
      |> Enum.reduce [], fn
        {_name, pid, :worker, _module}, acc -> [{make_ref, pid} | acc]
        _, acc -> acc
      end
  end

end


具有这些工作人员pid状态的GenServer:

defmodule Parallel.Process.Server do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, workers: [Parallel.Worker.Supervisor.workers])
  end
end


正如您在最后一行看到的那样,我正在调用“ Parallel.Worker.Supervisor.workers”,这似乎阻止了树的初始化等待,直到此方法返回后才完成。您如何将PID监督为GenServer的初始状态?

更新:

我不想使用poolboy(尽管这是一个很好的建议,请参阅源代码)可以帮助我了解更多信息。我并不想做任何特别的事情,我的工作人员只是简单地处理带有参数的传递函数。这是工人GenServer:

defmodule Parallel.Worker do
  use GenServer
  require Logger

  def start_link(state) do
    GenServer.start_link(__MODULE__, state, [])
  end

  def init(state) do
    {:ok, state}
  end

  # Using cast to be async as the job could take longer than the default 5 seconds,
  # Don't want client blocked on waiting for job to complete
  def handle_cast({:execute, fun, args, return_pid, job_ref}, state) do
    Logger.debug fn()-> "#{inspect self}: Recevied job with args: #{inspect args} for job #{inspect job_ref} to return to #{inspect return_pid}" end
    send(return_pid, {job_ref, apply(fun, args), self})
    {:noreply, state}
  end
end

最佳答案

我假设您要在此处创建某种类型的池?如评论中所述,您应该调查poolboy。如果出于练习的目的要自己实现此功能,仍然值得研究poolboy代码以获取启发。

从本质上讲,poolboy池是由“池管理器”管理的-gen_server维护着一组已知的工作程序。该池管理器进程在内部启动simple_one_for_one主管,该主管随后用于启动和监督工作程序。

池管理器进程during initialization first starts the supervisor。然后,它将prepopulate/1调用到start supervised worker processes。此函数将动态创建N个worker via supervisor:start_child/2,并且池管理器可以在内部保留worker pid列表。

这样可以确保在初始化过程中,池管理器进程无需与父主管进行对话(这是导致您陷入僵局的原因)。相反,管理者自己创建孩子。依靠内部主管仍然可以确保工人驻留在监督树中。

还需要其他一些精美的打印细节,以确保一切正常。 Poolboy进程(池管理器)将捕获出口,链接到监视器,并在检出它们时监视工作程序。这样可确保正确检测到工伤事故。我建议阅读代码以进行进一步分析。

我认为这是一个有趣的练习,可以使您更好地了解OTP。但是,如果要在生产中进行此操作,则直接使用poolboy可能会更好。

08-05 09:02