为什么在实现 GenServer 时跟踪#Reference 比跟踪#PID 更好?

Why it would be better keeping track of #Reference than #PID when implementing a GenServer?

Elixir 的 Mix and OTP Guide Chapter GenServer 解释了如何使用 GenServer.

实现注册服务器托管代理

每个代理的 PID 都保存在一个映射中,其中键是客户端提供的代理名称,值是代理的 PID。

为了避免保留对死代理的引用,该指南建议使用 Process.monitor/1 监控新创建的代理,并通过添加名为 refs 的新映射稍微修改状态,其中包含引用(值Process.monitor/1) 作为键返回,代理的名字作为值返回。它还显示了如何使用 handle_info/2 来处理监视消息以更新 refs.

Process.monitor/1 接收一个 PID(例如 #PID<0.66.0>)作为参数,returns 接收一个参考(例如 #Reference<0.0.0.551>)。 handle_info/2 捕获的 :DOWN 消息提供了 PID 和引用。

因为我们一直都知道这两个值:与在 refs 中使用 PID 相比,使用引用作为键有什么好处?

这是一个一致性问题。虽然您只监视进程,但没有区别。但是底层 :erlang.monitor/2 不能监控 只有 个进程:有端口等,基本上没有 PID.

来自文档:

Object

The monitored entity, which triggered the event. When monitoring a local process or port, Object will be equal to the pid() or port() that was being monitored. When monitoring process or port by name, Object will have format {RegisteredName, Node} where RegisteredName is the name which has been used with monitor/2 call and Node is local or remote node name (for ports monitored by name, Node is always local node name).

总结:Reference是一个实体,被监控。它可能是一个进程,一个端口,等等。虽然您不想 demonitor/1 关闭端口的整个过程被监视,但您应该使用引用。

如果您只有一个小项目并且只想监视一些进程,那么只记住 PID 就可以了。但我建议使用 Reference,因为项目增长很快,您可能不仅要监控流程。 Elixir 在 :erlang.monitor/2 下面使用,它还可以让你监控 Erlang 单调时间和 Erlang 系统时间之间的 portstime_offset。这可以在 Docs here 中找到。参考更通用,一般来说使用参考是更好的做法。

这里举个小例子我想说的是:

iex(1)> :erlang.monitor(:time_offset, :clock_service)
#Reference<0.0.3.100>

iex(2)> :erlang.monitor(:process ,self())
#Reference<0.0.3.113>