Elixir 发送一条消息,让多个GenServer同时运行,并知道它们都已运行?
我创建了许多GenServer,并使用PIDs一次向它们发送一条消息。但是,我希望他们在一个游戏中的一个“回合”上几乎同时行动。我如何才能:(1)向他们广播一条“开始!”消息(2)知道他们都演完了(即,结束了)?实现目标的一种方法是将所有的Elixir 发送一条消息,让多个GenServer同时运行,并知道它们都已运行?,elixir,Elixir,我创建了许多GenServer,并使用PIDs一次向它们发送一条消息。但是,我希望他们在一个游戏中的一个“回合”上几乎同时行动。我如何才能:(1)向他们广播一条“开始!”消息(2)知道他们都演完了(即,结束了)?实现目标的一种方法是将所有的开始消息抛出,然后异步回复: defmodule TurnTracker do use GenServer def init(pids) do state = %{ pids: pids, ongoing_reques
开始
消息抛出,然后异步回复:
defmodule TurnTracker do
use GenServer
def init(pids) do
state = %{
pids: pids,
ongoing_requests: MapSet.new()
}
{:ok, state}
end
# This will send out your go message to all genservers
def handle_call(:broadcast, _from, state) do
Enum.each(state.pids, fn pid ->
GenServer.cast(pid, {:go, self()})
end)
# The ongoing_requests is just a collection of all of the pids for the Player processes. One-by-one they will be removed using the handle_cast underneath here.
updated_state = Map.put(state, :ongoing_requests, MapSet.new(state.pids))
{:reply, :ok, updated_state}
end
# When one of the genservers is done its turn, it will send a message to here
def handle_cast({:completed_turn, pid}, state) do
# Remove the pid from the set, eventually we will remove them all
ongoing_requests = MapSet.delete(state.ongoing_requests, pid)
updated_state = Map.put(state, :ongoing_requests, ongoing_requests)
# Check to see if that was the last one, if it was, all of the Players are done their turns
if MapSet.size(ongoing_requests) == 0 do
# All of your GenServers are done
else
# One or more are not done yet
end
{:noreply, updated_state}
end
end
# You will have a bunch of these
defmodule Player do
use GenServer
def handle_cast({:go, turn_tracker_pid}, state) do
# Your turn logic here
# Tell the TurnTracker that we are done
GenServer.cast(turn_tracker_pid, {:completed_turn, self()})
{:noreply, state}
end
end
实际上,没有办法保证genserver会同时运行,因为当您发送消息时,您只是将消息放入他们的邮箱中,而您的邮箱前面可能还有其他消息
如果轮次超过5秒(Genserver.call的默认超时),则
:broadcast
将在此超时。对于最简单的解决方案,我将使Genserver
消息同步(使用call
而不是cast
发送消息)并为每台服务器启动一个任务
,等待结果(以避免阻止原始调用过程,并能够同时触发多条消息)。基本上是这样的:
servers
|> Enum.map(fn server -> Task.async(fn -> GenServer.call(server, :go) end) end)
|> Enum.map(&Task.await/1)
请注意,
Task.await
有一个默认超时,因此如果您的轮换时间很长,您可能需要增加该超时时间。我如何:(1)向他们广播“开始!”消息(2)知道他们已全部完成表演(即轮换结束)?如果GenServer收到“go”消息,然后立即崩溃怎么办?除非链接/监视GenServer进程,否则您将不知道GenServer是否仍在工作或崩溃。那么,如果GenServer崩溃,您想做什么?事情会变得相当复杂——这就是为什么会有OTP的原因……提出OTP的程序员在这些复杂的场景中做了所有的艰苦工作,你可以通过使用OTP来利用他们的辛勤工作。如果要重新启动崩溃的GenServer并使其完成工作,则可以使用OTP管理器。不过,尝试用手去做事情来接触复杂的事物并不是一件坏事。在解决方案的每次迭代中,都会有人指出您没有解决的边缘情况和缺陷。@7stud,我非常感谢您的建议,并将与OTP主管一起工作。现在我更关心的是时间而不是撞车。谢谢@Tyler。我想可能有一个特殊的功能,可以向多个PID进行广播。迭代集合是一种常见的解决方案。在你的例子中,玩家没有向TurnTracker注册,因此我不明白TurnTracker如何在每个回合开始时生成其PID列表。我将PID
列表添加到TurnTracker的init
,我想你可以在启动时传递它们(因此,首先启动所有玩家)。