【问题标题】:Transient gen_server processes and updating pids瞬态 gen_server 进程和更新 pid
【发布时间】:2011-05-09 21:40:11
【问题描述】:

我目前正在以合理的方式学习 Erlang,但对主管的 gen_server 有疑问。如果 gen_server 进程崩溃并因此被主管重新启动,它会收到一个新的 pid。现在,如果我希望其他进程通过 Pid 引用该进程怎么办?在这些过程中“更新” Pid 有哪些好的惯用方法?

作为一些实际应用的练习,我正在编写一个锁服务器,客户端可以使用任意键请求锁。理想情况下,我希望有一个单独的进程来处理特定锁的锁定和释放,我的想法是如果在 N 时间后没有人请求它,我可以使用 gen_server 中的 timeout 参数来终止进程,因此只有当前相关的锁将留在内存中。现在,我有一个将锁名称映射到锁进程的目录进程。当锁进程终止时,它会从目录中删除锁。

我关心的是如何处理客户端请求锁定而锁定进程正在终止的情况。它还没有关闭,所以嗅探 pid 是活着的是行不通的。锁定过程尚未到达将其从目录中删除的子句。

有没有更好的方法来处理这个问题?

编辑

目前有两个 gen_servers:'directory' 维护来自 LockName -> Lock Process 的 ETS 表,以及使用 start_child 动态添加到监督树的'lock servers'。理想情况下,我希望每个锁服务器直接处理与客户端的对话,但我担心当进程处于崩溃过程中时,获取/释放请求会被调用或强制转换发出的场景(因此不会响应到消息)。

以 {local} 或 {global} 开头将不起作用,因为它们可以有 N 个。

【问题讨论】:

    标签: multithreading erlang gen-server


    【解决方案1】:

    不要通过 pid 引用 gen_server 进程。

    您应该通过gen_server:call/2gen_server:call/3 函数为您的gen_server 提供API。他们接受ServerRef 作为第一个参数,可以是Name | {Name,Node} | {global,GlobalName} | pid()。所以,你的 API 看起来像:

    lock(Key) ->
      gen_server:call(?MODULE, {lock, Key}).
    release(Key) ->
      gen_server:call(?MODULE, {release, Key}).
    

    请注意,此 API 与您的 gen_server 在同一模块中定义,我假设您使用以下内容启动服务器:

    gen_server:start_link({local, ?MODULE}, ?MODULE, [], [])
    

    所以你的 API 方法可以不是通过 pid 而是通过服务器名称来查找服务器,它等于 ?MODULE

    更多信息请见gen_serverdocumentation

    【讨论】:

    • 这不会限制我只使用一个进程吗?我想要多个子进程的情况如何?
    • 是的,它会的。嗯...我以为您正在将 gen_server 进程用于“目录”进程,该进程管理锁处理程序(将其名称映射到处理锁的进程)。在这种情况下,您也不需要通过 pid 引用任何进程,“目录”进程(即我们的 gen_server)由其名称引用,并且锁定处理程序访问应仅通过“目录”进程的 API(@ 987654330@, release/1)。
    • 你是对的,目录进程实际上是这样处理的,注册到本地。我的目录 API 按照您的建议处理,因为无论如何我只想要一个进程。但是,我的锁定进程也是 gen_servers(所以我们有两个),它们是通过使用 start_child 的主管动态添加的。这些是我担心的,因为它们可以有任意数量,每个锁一个。
    • 我能想到两种方法:1) 你的锁处理程序应该尽可能简单:lock_handler(Key) -> receive release -> exit after ?LOCK_TIMEOUT -> release(Key) end.。 2)您应该使用目录服务器的一些内部API在失败后重新初始化目录中重新注册锁定处理程序。
    • 但是,实际上,我不喜欢锁定处理程序具有gen_server 行为的想法,因为 gen_servers 是已注册的进程,您将拥有任意数量的它们——每个键锁一个。这可能会在您的应用程序中引入内存泄漏,因为原子不会被垃圾收集(并且您将使用原子作为名称注册锁处理程序)。
    【解决方案2】:

    诀窍是给进程命名,而不是通过它的 pid 来引用它。您通常有 3 个可行的选择,

    • 使用注册名称。这就是 andreypopp 的建议。您通过其注册名称引用服务器。 本地注册名称必须是原子,这可能会限制您。 全局注册的名字没有这个限制,你可以注册任何术语。

    • Supervisor 知道 Pid。问它。您必须将 Supervisor Pid 传递给流程。

    • 或者,使用 gproc 应用程序(存在于http://github.com)。它允许您创建一个通用的流程注册表 - 您可以通过 ETS 来完成,但窃取好的代码而不是自己实现。

    如果所有进程都是同一监督树的一部分,则 pid 可用。因此,其中一个人的死亡意味着其他人的死亡。因此,Pids 回收无关紧要。

    【讨论】:

    • "全球注册的名字没有这个限制,你可以注册任何词" 不知道,可能有用。
    • 在这种情况下,如果我们在监督者上使用 start_child 将子项动态添加到监督树中,会怎样呢?注册到本地或全局将所有功能限制在一个进程中,不是吗?
    • 我个人使用gproc,所以我可以在gproc表中注册任何术语。然后,通过这个术语识别过程很容易。
    • 您好,这是一个迟到的评论,希望您能收到通知。如果我使用 gproc 遵循您的技术,并在一个函数中说我按其名称(如{bus, BusId})对 gen_server 进行 10 次调用,这意味着对 gproc 的 10 次调用,10 次 ETS 查找。那样可以么 ?你不觉得开销太大了吗?对我来说这似乎是一个完美的解决方案,但性能呢?
    • 忘记标记你@IGIVECRAPANSWERS,请看我之前的评论:)
    【解决方案3】:

    您可以通过使用“erlang:monitor/demonitor”API 完全避免使用“lock_server”进程。

    当客户端请求锁时,您发出锁.. 并在客户端上执行 erlang:monitor.. 这将返回一个 Monitor Reference.. 然后您可以将此引用与您的锁一起存储.. 美丽其中一个原因是当客户端死亡时,您的目录服务器会收到通知。您可以在客户端中实现 TIMEOUT。

    这是我最近编写的代码中的一个 sn-p.. https://github.com/xslogic/phoebus/blob/master/src/table_manager.erl

    基本上,table_manager 是一个将特定表资源锁定给客户端的进程。如果客户端死了,表将返回到池中。

    【讨论】:

      猜你喜欢
      • 2016-07-19
      • 2014-05-12
      • 2019-07-10
      • 1970-01-01
      • 1970-01-01
      • 2019-06-28
      • 2012-01-25
      • 2021-11-29
      • 1970-01-01
      相关资源
      最近更新 更多