Как реализовать паттерн ‘вещание’ (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 Это наиболее полный и гибкий подход. Создается центральный менеджер (в виде горутины), который управляет списком подписчиков (добавление/удаление) и рассылает им сообщения. Обычно это комбинация второго подхода с защитой доступа к списку подписчиков через мьютекс.