Отправить сообщение множеству Genserver'ов, чтобы они действовали одновременно и знали, что все они действовали?

Я создал много GenServer и отправляю им сообщения по одному, используя PID. Но я хочу, чтобы все они действовали примерно одновременно в один «ход» в игре. Как я могу: (1) транслировать "вперед!" сообщение для них (2) знаете, что все они закончили действовать (т.е. очередь окончена)?

Как я могу: (1) транслировать "вперед!" сообщение для них (2) знаете, что все они закончили действовать (т.е. очередь окончена)? Что, если GenServer получает сообщение "go", а затем сразу вылетает? Вы не узнаете, продолжает ли GenServer какую-то работу или он сломался - если вы не свяжете / не отслеживаете процесс GenServer. Тогда что вы хотите делать, если GenServer выйдет из строя? Все может быть довольно сложно - вот почему есть OTP.
7stud 17.12.2018 00:57

... Программисты, придумавшие OTP, проделали всю тяжелую работу в этих сложных сценариях, и вы можете усилить их тяжелую работу, используя OTP. Если вы хотите перезапустить вышедший из строя GenServer и дать ему завершить свою работу, вы можете использовать OTP Supervisor. Тем не менее, это неплохое упражнение - пытаться делать что-то вручную, чтобы понять всю сложность. На каждой итерации вашего решения кто-нибудь может указать на крайние случаи и недостатки, которые вы не устраняете.

7stud 17.12.2018 00:59

@ 7stud, я ценю этот совет и буду работать с OTP Supervisor. Прямо сейчас меня больше волнует время, чем сбой.

davideps 17.12.2018 07:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
194
2

Ответы 2

Один из способов добиться желаемого - это отправить cast все сообщения go, а затем ответить асинхронно:

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

На самом деле нет способа гарантировать, что ваши GenServers будут действовать одновременно, потому что, когда вы отправляете сообщение, вы просто помещаете сообщение в их почтовые ящики, и перед вами могут быть другие сообщения.

Если повороты занимают более 5 секунд (тайм-аут по умолчанию для Genserver.call), то здесь :broadcast отключится.

Спасибо @Tyler. Я подумал, что может быть специальная функция для трансляции на несколько PID. Итерация по коллекции - знакомое решение. В вашем примере Player не регистрируется в TurnTracker, поэтому я не понимаю, как TurnTracker генерирует свой список PID в начале каждого хода.

davideps 17.12.2018 07:35

Я добавил список pids в init TurnTracker, я решил, что вы можете передать их при запуске (так что сначала запустите все плееры).

Tyler 17.12.2018 15:06

Для простейшего решения я бы сделал сообщение GenServer синхронным (используя call вместо cast для отправки сообщения) и запустил Task для каждого сервера, чтобы дождаться результата (чтобы не блокировать исходный процесс вызова и иметь возможность запускать много сообщений одновременно). В основном примерно так:

servers
|> Enum.map(fn server -> Task.async(fn -> GenServer.call(server, :go) end) end)
|> Enum.map(&Task.await/1)

Обратите внимание, что Task.await имеет тайм-аут по умолчанию, поэтому, если ваши ходы очень длинные, вы можете увеличить его.

Другие вопросы по теме