Ответ
Условная переменная (sync.Cond
) — это примитив синхронизации, который позволяет горутинам ждать (блокироваться) до тех пор, пока не будет выполнено некоторое условие. Она всегда используется в связке с мьютексом (sync.Mutex
или sync.RWMutex
).
Основная идея: одна или несколько горутин могут приостановить свое выполнение с помощью cond.Wait()
, пока другая горутина не изменит условие и не оповестит ожидающих с помощью cond.Signal()
или cond.Broadcast()
.
Ключевые методы:
cond.Wait()
: Атомарно разблокирует связанный мьютекс и переводит горутину в режим ожидания. После пробуждения она снова блокирует мьютекс перед тем, как продолжить выполнение.cond.Signal()
: Пробуждает одну (случайную) горутину, ожидающую наcond
.cond.Broadcast()
: Пробуждает все горутины, ожидающие наcond
.
Когда использовать?
Чаще всего sync.Cond
применяется в сценариях типа "производитель-потребитель" (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()
// Важно использовать цикл для проверки условия из-за "ложных пробуждений" (spurious wakeups)
for len(queue) == 0 {
fmt.Println("Потребитель: очередь пуста, жду...")
cond.Wait() // Блокируется, пока производитель не добавит элемент
}
item := queue[0]
queue = queue[1:]
fmt.Printf("Потребитель: обработал %d, осталось в очереди: %dn", item, len(queue))
mu.Unlock()
}
}()
// Производитель (producer)
for i := 0; i < 5; i++ {
time.Sleep(1 * time.Second)
mu.Lock()
queue = append(queue, i)
fmt.Printf("Производитель: добавил %d в очередьn", i)
cond.Signal() // Оповещаем одного ждущего потребителя
mu.Unlock()
}
time.Sleep(2 * time.Second)
}
Важные правила:
- Всегда блокируйте мьютекс перед вызовом
Wait()
,Signal()
илиBroadcast()
. - Всегда проверяйте условие в цикле
for
, а не вif
, чтобы корректно обработать ложные пробуждения и гарантировать, что условие действительно выполнено.