【问题标题】:Elixir - GenServer with initial state of several other GenServer pidsElixir - 具有其他几个 GenServer pid 初始状态的 GenServer
【发布时间】:2015-01-17 19:40:40
【问题描述】:

我在自动启动监督树时遇到了死锁问题。一个 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

【问题讨论】:

  • 也许您需要重新架构应用程序而不显式传递工作进程 pid。您真正想在这里完成什么?
  • 您是否正在寻找像 Poolboy 这样的池化库?查看这篇文章:hashnuke.com/2013/10/03/…

标签: elixir


【解决方案1】:

我假设您想在这里创建某种池?如 cmets 中所述,您应该研究 poolboy。如果为了练习而想自己实现它,仍然值得研究 poolboy 代码以获得灵感。

从本质上讲,poolboy 池由“池管理器”管理 - 一个 gen_server 维护一组已知工作人员。这个池管理器进程在内部启动了一个 simple_one_for_one 主管,然后用于启动和监督工作人员。

池管理器进程during initialization first starts the supervisor。然后,它将prepopulate/1 调用到start supervised worker processes。这个函数会动态创建N个workervia supervisor:start_child/2,pool manager内部可以保存worker pid列表。

这可确保池管理器进程在初始化期间不需要与父主管对话(这是导致死锁的原因)。相反,经理自己创建孩子。依靠内部主管仍可确保工人驻留在监督树中。

还有一些其他细节需要确保一切正常。 Poolboy 进程(池管理器)将捕获出口,链接到监视器,并在签出时监视工作人员。这确保了正确检测工人崩溃。我建议阅读代码以进行进一步分析。

我的观点是,这可能是一个有趣的练习,可以更好地理解 OTP。但是,如果您是在生产环境中这样做,那么直接使用 poolboy 可能会更好。

【讨论】:

  • 感谢您的解释,这对理解 poolboy 代码很有帮助。正如您所说,我这样做是为了学习目的,以便更好地了解 OTP。
  • 是的,我建议逐步进行。首先,您可以只实现一个池管理器,它直接从它的 init/1 创建工人,而无需使用主管。一旦成功,您可能会引入一个 simple_one_for_one 来处理监督。然后,您可以开始监视工作人员以了解他们何时崩溃。最后,您可以在工作人员上添加“按需”创建 - 按需创建并在客户端完成工作后终止的进程。
猜你喜欢
  • 2017-09-29
  • 1970-01-01
  • 2018-12-14
  • 2018-04-04
  • 2017-10-16
  • 1970-01-01
  • 2021-02-15
  • 1970-01-01
  • 2019-01-02
相关资源
最近更新 更多