Ответ
sync.Cond
(условная переменная) — это примитив синхронизации, который позволяет горутинам ожидать наступления определенного события или изменения условия. Он всегда используется вместе с sync.Mutex
.
Назначение:
sync.Cond
нужен, когда горутине нужно не просто дождаться разблокировки мьютекса, а дождаться, пока некое разделяемое состояние (защищенное этим мьютексом) не примет нужную форму.
Пример: Очередь с ожиданием
Представим, что у нас есть очередь, и несколько горутин-потребителей хотят забрать из нее элемент. Если очередь пуста, они должны уснуть и ждать, пока горутина-производитель не добавит что-то в очередь.
package main
import (
"fmt"
"sync"
"time"
)
var (
mu sync.Mutex
cond = sync.NewCond(&mu)
queue []int
)
// Потребитель
func consumer(id int) {
mu.Lock()
// Важно использовать цикл для проверки условия!
for len(queue) == 0 {
fmt.Printf("Consumer %d is waiting...n", id)
cond.Wait() // 1. Атомарно разблокирует мьютекс и усыпляет горутину
// 2. Когда горутину разбудят, она снова пытается заблокировать мьютекс
}
item := queue[0]
queue = queue[1:]
fmt.Printf("Consumer %d consumed %dn", id, item)
mu.Unlock()
}
// Производитель
func producer() {
time.Sleep(2 * time.Second) // Имитация работы
mu.Lock()
queue = append(queue, 1)
fmt.Println("Producer added an item, signaling...")
cond.Signal() // Будит ОДНУ случайную горутину, ожидающую на cond.Wait()
// cond.Broadcast() // Разбудил бы ВСЕ ожидающие горутины
mu.Unlock()
}
func main() {
go consumer(1)
go consumer(2)
go producer()
time.Sleep(3 * time.Second)
}
Ключевые правила использования:
Wait()
всегда в цикле: Горутина может быть разбужена ложно (spurious wakeup). Поэтому после пробуждения всегда нужно заново проверять условие (for !condition { cond.Wait() }
).- Блокировка мьютекса: Мьютекс должен быть заблокирован перед вызовом
Wait()
,Signal()
илиBroadcast()
. Signal()
vsBroadcast()
:Signal()
: Будит одну (любую) из ожидающих горутин. Используется, когда любой из потребителей может выполнить работу (как в примере с очередью).Broadcast()
: Будит все ожидающие горутины. Используется, когда событие может быть важным для всех (например, изменение конфигурации, которое все должны перечитать).
Когда использовать sync.Cond
, а когда — каналы?
sync.Cond
: Идеален, когда несколько горутин ждут изменения состояния, защищенного мьютексом. Особенно полезенBroadcast
для оповещения многих.- Каналы: Лучше подходят для прямой коммуникации между горутинами, передачи владения данными или для простой синхронизации (сигнал "готово").