Какие знаешь способы взаимодействия между горутинами, кроме каналов?

Ответ

В Go горутины могут взаимодействовать не только через каналы, но и другими способами. Выбор метода зависит от конкретной задачи и требований к синхронизации:

  1. sync.Mutex / sync.RWMutex - Мьютексы используются для обеспечения безопасного доступа к общим данным, предотвращая состояния гонки (race conditions). RWMutex позволяет множественные чтения, но эксклюзивную запись.

    var mu sync.Mutex
    var counter int
    
    func incrementCounter() {
        mu.Lock()
        defer mu.Unlock()
        counter++
    }
  2. sync.WaitGroup - Используется для ожидания завершения группы горутин. Это полезно, когда нужно дождаться выполнения всех параллельных задач.

    var wg sync.WaitGroup
    wg.Add(2) // Ожидаем завершения двух горутин
    
    go func() {
        defer wg.Done() // Уменьшаем счетчик по завершении
        // Выполняем работу горутины 1
    }()
    
    go func() {
        defer wg.Done() // Уменьшаем счетчик по завершении
        // Выполняем работу горутины 2
    }()
    
    wg.Wait() // Блокируем выполнение, пока счетчик не станет 0
  3. sync.Once - Гарантирует, что функция будет выполнена ровно один раз, независимо от того, сколько раз она будет вызвана. Часто используется для инициализации синглтонов или других ресурсов.

    var once sync.Once
    
    func initializeResource() {
        once.Do(func() {
            // Этот код выполнится только один раз
            fmt.Println("Ресурс инициализирован")
        })
    }
  4. Атомарные операции (sync/atomic) - Предоставляют низкоуровневые, высокопроизводительные операции для атомарного изменения примитивных типов данных (например, int32, int64, uint32, uint64, Pointer). Они не используют мьютексы и часто быстрее для простых операций.

    var count int32
    atomic.AddInt32(&count, 1) // Атомарно увеличиваем count на 1
  5. Контекст (context.Context) - Используется для передачи дедлайнов, сигналов отмены и других значений между горутинами в дереве вызовов. Это мощный механизм для управления жизненным циклом связанных операций.

    ctx, cancel := context.WithCancel(context.Background())
    go func(ctx context.Context) {
        select {
        case <-ctx.Done():
            fmt.Println("Горутина отменена:", ctx.Err())
            return
        // ... работа горутины
        }
    }(ctx)
    
    // Через некоторое время
    cancel() // Отменяем контекст, сигнализируя горутине завершиться
  6. Глобальные переменные (с осторожностью и обязательной синхронизацией) - Хотя технически возможно, использование глобальных переменных для взаимодействия требует тщательной синхронизации (например, с помощью мьютексов или атомарных операций), чтобы избежать состояний гонки. Это наименее идиоматичный и наиболее подверженный ошибкам способ.

Каналы являются идиоматичным и часто наиболее безопасным способом взаимодействия, следуя принципу "Do not communicate by sharing memory; instead, share memory by communicating." Однако, в некоторых случаях, другие подходы могут быть более эффективными или удобными, особенно для управления доступом к общим ресурсам или координации выполнения.