Как реализовать паттерн ‘вещание’ (broadcast) в Go?

Ответ

В Go паттерн 'вещание' (broadcast) можно реализовать несколькими способами, каждый со своими особенностями:

  1. Закрытие канала (one-time broadcast) Это самый простой способ. При закрытии канала все горутины, ожидающие чтения из него, немедленно разблокируются и получат нулевое значение типа канала.

    • Плюсы: Простота реализации.
    • Минусы: Это одноразовый сигнал. После закрытия канал нельзя использовать для отправки данных.
      
      broadcastChan := make(chan struct{})

    // В подписчиках: go func() { <-broadcastChan fmt.Println("Горутина 1 получила сигнал!") }()

    // Вещатель: close(broadcastChan) // Все горутины получат сигнал

  2. Рассылка по нескольким каналам (Fan-out) Создается отдельный канал для каждого подписчика, и вещатель отправляет сообщение в каждый из них.

    • Плюсы: Позволяет передавать данные, а не только сигналы.
    • Минусы: Если один из каналов-получателей не готов принять сообщение (заблокирован), то весь цикл рассылки остановится. Эту проблему можно решить, используя неблокирующую отправку с select.
      // subscribers - это slice или map с каналами подписчиков
      for _, ch := range subscribers {
      // Неблокирующая отправка, чтобы не зависнуть на одном подписчике
      select {
      case ch <- message:
      default: // Сообщение будет пропущено, если подписчик не готов
      }
      }
  3. Использование sync.Cond sync.Cond — это условная переменная, которая позволяет горутинам ждать наступления определенного события. Это более низкоуровневый и производительный механизм для синхронизации.

    • cond.Broadcast() — пробуждает все ожидающие горутины.
    • cond.Signal() — пробуждает одну случайную горутину.
    • Плюсы: Высокая производительность, предназначен специально для таких задач.
      
      var mu sync.Mutex
      cond := sync.NewCond(&mu)

    // Получатель (в горутине): cond.L.Lock() cond.Wait() // Ждет сигнала // ... делает работу cond.L.Unlock()

    // Вещатель: cond.Broadcast() // Пробуждает всех

  4. Реализация паттерна Pub/Sub Это наиболее полный и гибкий подход. Создается центральный менеджер (в виде горутины), который управляет списком подписчиков (добавление/удаление) и рассылает им сообщения. Обычно это комбинация второго подхода с защитой доступа к списку подписчиков через мьютекс.

Ответ 18+ 🔞

А, паттерн вещания в Go, да? Ну это ж классика, как "Муму" Тургенева, только без утопления собаки, блядь.

Смотри, вариантов дохуя, и каждый со своей, блядь, философией. Как в том анекдоте про ткачиху — можно просто зашить, а можно полотна наткать, чтобы все заеблись.

Первый способ — закрытие канала. Это как одноразовый сигнал, ёпта. Закрыл канал — и все, кто из него читал, тут же просыпаются. Просто, как три копейки, но и одноразово, блядь, как спичка.

broadcastChan := make(chan struct{})
// В подписчиках:
go func() {
    <-broadcastChan // Сидим, ждём, как Герасим у лестницы
    fmt.Println("Горутина 1 получила сигнал!")
}()
// Вещатель:
close(broadcastChan) // Хлоп! И все побежали

Плюсы? Да нихуя сложного. Минусы? После этого канал — труп, в него больше ни хуя не отправишь.

Второй способ — рассылка по куче каналов (Fan-out). Тут уже посерьёзнее. У каждого подписчика свой личный канал, и ты, как почтальон Печкин, бегаешь и всем суёшь сообщение в щель.

for _, ch := range subscribers {
    select {
    case ch <- message: // Отправил — и молодца
    default: // А этот, сука, не готов, засранец. Ну и хуй с ним, пропускаем.
    }
}

Плюс — можно данные передавать, а не только пинок под зад. Минус — если один подписчик тормозит и не читает, то на нём весь цикл встанет колом, если без select. А с select и default — проебёшь сообщение. Волнение ебать!

Третий способ — sync.Cond. Вот это, блядь, тяжёлая артиллерия. Низкоуровневая синхронизация, овердохуища производительности. Условная переменная, мать её.

  • cond.Broadcast() — это как крикнуть на всю деревню: "ПОЖАААР!" — все высыпят.
  • cond.Signal() — это как ткнуть одного в бок: "Проснись, мудак".
    var mu sync.Mutex
    cond := sync.NewCond(&mu)
    // Получатель:
    cond.L.Lock()
    cond.Wait() // Сижу, курю, жду у моря погоды
    cond.L.Unlock()
    // Вещатель:
    cond.Broadcast() // ВСЕМ ВСТАТЬ! СУД ИДЁТ!

    Плюсы — быстрее некуда, для этого и создан. Минусы — с низкоуровневыми примитивами всегда можно так накосячить, что мало не покажется. Подозрение ебать чувствую, когда его используют.

Четвёртый способ — полноценный Pub/Sub. Это уже, блядь, целый театр с директором. Отдельная горутина-менеджер, которая держит список всех подписчиков, охраняемый мьютексом, как зеницу ока. Добавляй, удаляй, рассылай — красота. Это комбинация второго способа, но причёсанная и поставленная на поток. Гибко, мощно, но и кода писать — овердохуища.

Вот и вся история, как с Герасимом. Можно просто и грубо — закрыть канал и утопить всех в озере. А можно выстроить целую систему отношений с подписчиками, где каждому — по письму, каждому — по сигналу. Выбирай, что душе угодно, только смотри, чтобы в конце не оказаться тем самым мудаком, который спрашивает: "Что же я, блядь, наделал?"