我有一个使用 2 个 GenServers 的项目 第一个名为"State"的GenServer维护状态,第二个GenServer名为"Updates"维护状态的可能更新列表。我想要实现的是:
每次调用"状态","状态">都应调用"更新"并在返回实际状态之前更新自身。
两个GenServer都是由主管启动的,我可以从外部按名称调用两个GenServer,但是如果我尝试在"状态"内部调用"更新"的API,则"状态"将以"无进程"错误终止。有什么建议吗?
def start_link(opts) do
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
def init(_arg) do
children = [
{Updates, name: Updates},
{State, name: State}
]
Supervisor.init(children, strategy: :rest_for_one)
end
两个GenServer都以
def start_link(opts) do
GenServer.start_link(__MODULE__, [], opts)
end
在"状态"中,我有回调
@impl true
def handle_call({:get}, _from, state) do
updates = Updates.get_updates(Updates)
{:reply, updates, state}
end
同样,如果我直接从 iex 调用 Updates.get_updates(更新),一切都按预期工作,所以我认为我的主管一切都很好。似乎就像"状态"不知道"更新"的名称一样。
Updates.get_updates/1 实现为:
def get_updates(pid) do
GenServer.call(pid, :get)
end
其中回调只是回复的状态
@impl true
def handle_call(:get, _from, state) do
{:reply, state, state}
end
State"以"无进程"错误终止。有什么建议吗?
根据主管文档,children
列表:
children = [
{Updates, name: Updates},
{State, name: State}
]
应该是child specification
元组的列表,其中子规范具有以下有效键:
子规范包含 6 个键。前两个是必需的, 其余的都是可选的:
:id- 用于在内部标识子规范的任何术语 主管;默认为给定模块。在以下情况下: 冲突的 :id 值,主管将拒绝初始化和 需要显式 ID。此密钥是必需的。
:start- 一个元组,其中包含要调用的模块函数参数来启动 子进程。此密钥是必需的。
:restart- 定义终止的子进程何时应 重新启动(请参阅下面的"重新启动值"部分)。此键是 可选,默认为 :p。
:shutdown- 定义子进程应该如何的原子 已终止(请参阅下面的"关机值"部分)。此键是 可选,如果类型为 :worker 或 :无穷大,则默认为 5000,如果 类型为 :主管。
:type- 指定子进程是 :worker 或 :主管。此键是可选的,默认为 :worker。
还有第六个键:modules,它很少改变。它被设定 自动基于 :start 中的值。
请注意,没有name:
密钥,您在子规范中列出了该密钥。
但是,GenServer.start_link()
确实有一个name:
选项:
start_link/3 和 start/3 都支持 GenServer 注册名称 通过 :name 选项启动。注册名称也将自动生效 终止时已清理。支持的值包括:
一个原子- GenServer 使用 Process.register/2 在本地注册给定名称。
{:global, term}- GenServer 使用 :global 模块中的函数使用给定术语全局注册。
{:via, module, term}- GenServer 使用给定的机制和名称注册。:via 选项需要一个导出的模块 register_name/2、unregister_name/1、whereis_name/1 和发送/2。一 这样的例子是 :global 模块,它使用这些函数 保留进程及其关联 PID 的名称列表 可用于全球Elixir节点网络。长生不老药也 附带一个本地的、分散的和可扩展的注册表,称为 用于本地存储动态生成的名称的注册表。
例如,我们可以在本地启动并注册我们的 Stack 服务器 遵循:
# Start the server and register it locally with name: MyStack {:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)
所以,我认为你应该做这样的事情:
def init(_arg) do
children = [
Updates,
State
]
然后在您的 GenServer start_link() 函数中:
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
======
这是一个完整的示例。 在application.ex
中,您可以指定要注册的名称:
children = [
# Starts a worker by calling: Servers.Worker.start_link(arg)
# {Servers.Worker, arg},
{
Servers.CurrentState, [
init_state_with: [:hello, 10],
name_to_register: Servers.CurrentState
]
},
{
Servers.Updates, [
init_state_with: [:goodbye],
name_to_register: Servers.Updates
]
}
]
然后你可以像这样定义你的两个GenServers:
lib/servers/updates.ex:
defmodule Servers.Updates do
use GenServer
def start_link(arg) do
GenServer.start_link(
__MODULE__,
arg[:init_state_with],
name: arg[:name_to_register])
end
## Callbacks
@impl true
def init(state) do
{:ok, state}
end
@impl true
def handle_call(:get_updates, _from, state) do
{:reply, state, state}
end
@impl true
def handle_cast({:push, item}, state) do
{:noreply, [item | state]}
end
##User interface:
def get() do
GenServer.call(__MODULE__, :get_updates)
end
def add(item) do
GenServer.cast(__MODULE__, {:push, item})
end
end
lib/servers/current_state.ex:
defmodule Servers.CurrentState do
use GenServer
def start_link(args) do
GenServer.start_link(
__MODULE__,
args[:init_state_with],
name: args[:name_to_register])
end
## Callbacks
@impl true
def init(state) do
IO.inspect(state, label: "The CurrentState server is starting with state")
{:ok, state}
end
@impl true
def handle_call(:get_state, _from, state) do
state_to_add = Servers.Updates.get()
new_state = state_to_add ++ state
{:reply, new_state, new_state}
end
##User interface:
def get() do
GenServer.call(__MODULE__, :get_state)
end
end
然后,您可以使用以下内容进行测试:
defmodule Servers.Go do
def test() do
IO.inspect("Updates has state: #{inspect Servers.Updates.get()}" )
IO.inspect("CurrentState has state: #{inspect Servers.CurrentState.get()}" )
:ok
end
end
在 iex 中:
~/elixir_programs/servers$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Compiling 1 file (.ex)
The CurrentState server is starting with state: [:hello, 10]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Servers.Go.test()
"Updates has state: [:goodbye]"
"CurrentState has state: [:goodbye, :hello, 10]"
:ok
iex(2)>
(请注意,输出的第一行与服务器启动消息混合在一起。
但是,您可以使用__MODULE__
来简化操作:
应用程序.ex:
children = [
# Starts a worker by calling: Servers.Worker.start_link(arg)
# {Servers.Worker, arg},
{ Servers.CurrentState, [:hello, 10] }
{ Servers.Updates, [:goodbye] }
]
lib/servers/updates.ex:
defmodule Servers.Updates do
use GenServer
def start_link(arg) do
#arg comes from child specification tuple
#inside the `children` list in application.ex
# | module where the GenServer is defined
# |
# | | send arg to the GenServer's init() function
# V V
GenServer.start_link(__MODULE__, arg, name: __MODULE__)
# ^
# |
# register the specified name for this GenServer
end
## Callbacks
@impl true
def init(state) do
{:ok, state}
end
@impl true
def handle_call(:get_updates, _from, state) do
{:reply, state, state}
end
@impl true
def handle_cast({:push, item}, state) do
{:noreply, [item | state]}
end
## User interface:
def get() do
GenServer.call(__MODULE__, :get_updates)
end
def add(item) do
GenServer.cast(__MODULE__, {:push, item})
end
end
lib/servers/current_state.ex:
defmodule Servers.CurrentState do
use GenServer
def start_link(arg) do
#arg comes from child specification tuple
#inside the `children` list in application.ex
# | module where the GenServer is defined
# |
# | | send arg to the GenServer's init() function
# V V
GenServer.start_link(__MODULE__, arg, name: __MODULE__)
# ^
# |
# register the specified name for this GenServer
end
## Callbacks
@impl true
def init(state) do
IO.inspect(state, label: "The CurrentState server is starting with state")
{:ok, state}
end
@impl true
def handle_call(:get_state, _from, state) do
state_to_add = Servers.Updates.get()
new_state = state_to_add ++ state
{:reply, new_state, new_state}
end
## User interface:
def get() do
GenServer.call(__MODULE__, :get_state)
end
end