← Назад к вопросам

Что такое sync.Cond?

1.7 Middle🔥 61 комментариев
#Конкурентность и горутины#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое sync.Cond?

sync.Cond — это тип из стандартной библиотеки Go, предоставляющий механизм условной переменности (condition variable) для синхронизации горутин. Он используется, когда горутины должны ожидать или сигнализировать о выполнении определенного условия, связанного с изменением состояния разделяемых данных. Это классический инструмент для реализации паттерна "ожидание события" в многопоточной среде.

Основная идея и аналогия

По своей сути, sync.Cond — это более высокоуровневый инструмент, чем простые мьютексы (sync.Mutex). Если мьютексы отвечают за исключительный доступ к данным, то Cond управляет синхронизацией на основе логических условий. Например, одна горутина ожидает, когда другая закончит вычисления или когда в буфере появится элемент. Это похоже на Wait и Signal в других языках (например, в pthreads).

Ключевые методы и структура

Тип sync.Cond имеет три основных метода:

// Ожидание сигнала. Горутина блокируется до вызова Broadcast или Signal.
func (c *Cond) Wait()

// Сигнализирует одной ожидающей горутине (разблокирует одну).
func (c *Cond) Signal()

// Сигнализирует всем ожидающим горутинам (разблокирует все).
func (c *Cond) Broadcast()

Важнейшее правило: Все методы Cond должны вызываться внутри защищенной области мьютекса, который является частью структуры Cond. sync.Cond создается с использованием sync.NewCond, который принимает Locker (чаще всего &sync.Mutex{}):

var mu sync.Mutex
cond := sync.NewCond(&mu)

Типичный пример использования

Рассмотрим классический пример: производитель (producer) и потребитель (consumer).

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var mu sync.Mutex
    cond := sync.NewCond(&mu)
    queue := make([]int, 0)

    // Consumer - ожидает элемент в очереди
    go func() {
        for {
            mu.Lock()
            // Ожидаем, пока очередь не станет непустой
            for len(queue) == 0 {
                cond.Wait() // Wait автоматически отпускает мьютекст и блокирует горутину
            }
            item := queue[0]
            queue = queue[1:]
            fmt.Println("Consumer взял:", item)
            mu.Unlock()
        }
    }()

    // Producer - добавляет элементы и сигнализирует
    go func() {
        for i := 1; i <= 5; i++ {
            time.Sleep(1 * time.Second)
            mu.Lock()
            queue = append(queue, i)
            fmt.Println("Producer добавил:", i)
            cond.Signal() // Сигнализируем одному потребителю
            mu.Unlock()
        }
    }()

    time.Sleep(6 * time.Second)
}

Как работает Wait()

Внутри Wait() выполняет следующую последовательность:

  1. Автоматически отпускает связанный мьютекст (чтобы другие горутины могли изменить состояние).
  2. Блокирует горутину до получения сигнала (Signal или Broadcast).
  3. Перед возвратом перезахватывает мьютекст (так что после Wait() вы остаетесь в защищенной области).

Почему условие проверяется в цикле?

В примере выше мы используем for len(queue) == 0, а не if. Это критически важно для избежания ложных пробуждений (spurious wakeups). Система может разбудить горутину даже без явного сигнала, или состояние может измениться между пробуждением и захватом мьютекста. Цикл гарантирует повторную проверку условия после пробуждения.

Когда использовать sync.Cond?

  • Ожидание конкретного состояния данных: Например, очередь стала непустой, ресурс освободился, вычисление завершилось.
  • Координация нескольких горутин: Когда одна горутина должна "разбудить" группу других (Broadcast).
  • Реализация пулов, ограниченных буферов, барьеров.

Альтернативы и сравнение

  • Каналы (channels): Часто являются более простой и безопасной альтернативой для сигнализации событий. Однако Cond может быть эффективнее, когда нужно синхронизировать множество горутин на одном условии или когда состояние связано со сложной структурой данных.
  • sync.WaitGroup: Для ожидания завершения группы задач, но не для условий на основе данных.
  • Активные ожидания (спинлоки): Неэффективны и не рекомендуются.

Основные предостережения

  • Требуется мьютекст: Все операции с Cond должны быть защищены.
  • Ложные пробуждения: Обязательно используйте цикл для проверки условия.
  • Сложность: Более сложен для понимания и корректного использования, чем каналы. Часто является выбором экспертов в низкоуровневой синхронизации.

В заключение, sync.Cond — это мощный, но нишевый инструмент для сложных сценариев синхронизации, где необходимо эффективно ждать изменения состояния разделяемых данных. Его использование требует глубокого понимания многопоточности и аккуратности, чтобы избежать ошибок и гонок данных.

Что такое sync.Cond? | PrepBro