我有一个使用2个GenServer的项目 第一个名为“状态”的GenServer维护状态,第二个名为“更新”的GenServer维护状态的可能更新列表。我想要实现的是:
每次调用“状态”时,“状态”应调用“更新”并在返回实际状态之前进行更新。
两个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),一切都会按预期运行,因此我认为对我的主管来说一切都很好。似乎“州”不知道“更新”的名称。
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
答案 0 :(得分:1)
状态”以“无进程”错误终止。有任何建议吗?
根据Supervisor docs,children
列表:
children = [
{Updates, name: Updates},
{State, name: State}
]
应该是child specification
元组的列表,其中子规范具有以下有效键:
子规范包含6个键。前两个是必填项, 其余的是可选的:
:id -用于在内部标识子规范的任何术语 主管;默认为给定的模块。如果是 :id值冲突,主管将拒绝初始化和 需要明确的ID。该密钥是必需的。
:start -具有要启动的module-function-args的元组 子进程。该密钥是必需的。
:重新启动-一个原子,它定义何时终止子进程 重新启动(请参阅下面的“重新启动值”部分)。这个钥匙是 可选,默认为:permanent。
:关机-一个原子,它定义子进程的方式 终止(请参见下面的“关闭值”部分)。这个钥匙是 可选,如果类型为:worker或:infinity,则默认为5000 类型是:主管。
:类型-指定子进程是:worker还是 :主管该密钥是可选的,默认为:worker。
有第六个键,:模块,该键很少更改。设置 自动根据:start中的值。
请注意,没有name:
键,您要在子说明中列出该键。
但是,GenServer.start_link()
确实有一个name:
选项:
start_link / 3和start / 3都支持GenServer注册名称 首先通过:name选项。注册名称也自动 在终止时清理。支持的值是:
一个原子-GenServer使用Process.register / 2在本地注册了给定名称。
{:global,term} -使用:global模块中的功能,以给定的术语对GenServer进行全局注册。
{:通过模块,术语} -GenServer已使用给定的机制和名称进行注册。 :via选项需要一个导出的模块 register_name / 2,unregister_name / 1,isis_name / 1和send / 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
]
}
]
然后,您可以这样定义两个GenServer:
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__
来简化操作:
application.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