Что такое Condition Variable?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Condition Variable (Переменная условия)
Condition Variable — это примитив синхронизации, который позволяет одной или нескольким горутинам ждать до тех пор, пока не произойдёт определённое событие, и других горутинам сигнализировать об этом событии.
Концепция
Condition Variable объединяет мьютекс и механизм ожидания/сигнала:
- Горутина блокируется и освобождает мьютекс
- Другая горутина может изменить состояние и разбудить ожидающую горутину
- Когда горутина пробуждается, она снова захватывает мьютекс
Реализация в Go с sync.Cond
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var mu sync.Mutex
cond := sync.NewCond(&mu)
ready := false
// Горутина 1: ждёт, пока ready станет true
go func() {
mu.Lock()
for !ready { // Важно: используем for, а не if
cond.Wait() // Ожидает и освобождает мьютекс
}
fmt.Println("Горутина 1: готово!")
mu.Unlock()
}()
// Горутина 2: устанавливает ready и сигнализирует
go func() {
time.Sleep(1 * time.Second)
mu.Lock()
ready = true
cond.Signal() // Разбудить одну ожидающую горутину
mu.Unlock()
}()
time.Sleep(2 * time.Second)
}
Методы sync.Cond
1. Wait() — ожидание сигнала
cond.Wait()
// - Освобождает мьютекс
// - Блокируется до получения сигнала
// - При пробуждении заново захватывает мьютекс
2. Signal() — разбудить одну горутину
cond.Signal()
// Разбудит ровно одну горутину из ждущих
// Если нет ждущих, сигнал теряется (non-sticky)
3. Broadcast() — разбудить всех
cond.Broadcast()
// Разбудит все ждущие горутины
Практический пример: Пул рабочих
type Pool struct {
mu sync.Mutex
items []string
notEmpty *sync.Cond
notFull *sync.Cond
maxSize int
}
func NewPool(maxSize int) *Pool {
mu := &sync.Mutex{}
p := &Pool{
mu: *mu,
items: []string{},
notEmpty: sync.NewCond(mu),
notFull: sync.NewCond(mu),
maxSize: maxSize,
}
return p
}
func (p *Pool) Put(item string) {
p.mu.Lock()
// Ждём, пока очередь не станет свободной
for len(p.items) >= p.maxSize {
p.notFull.Wait()
}
p.items = append(p.items, item)
p.notEmpty.Signal() // Пробудить потребителя
p.mu.Unlock()
}
func (p *Pool) Get() string {
p.mu.Lock()
// Ждём, пока очередь не станет непуста
for len(p.items) == 0 {
p.notEmpty.Wait()
}
item := p.items[0]
p.items = p.items[1:]
p.notFull.Signal() // Пробудить производителя
p.mu.Unlock()
return item
}
Важные моменты
1. Всегда используй for, а не if
// ❌ Неправильно
mu.Lock()
if !condition {
cond.Wait()
}
// Может быть spurious wakeup
// ✅ Правильно
mu.Lock()
for !condition {
cond.Wait()
}
mu.Unlock()
2. Мьютекс должен быть захвачен перед Wait()
// ✅ Правильно
mu.Lock()
for !condition {
cond.Wait()
}
mu.Unlock()
// ❌ Неправильно — паника
// cond.Wait() без захвата мьютекса
3. Spurious wakeup — горутина может проснуться без сигнала
Поэтому ВСЕГДА проверяй условие в цикле for.
Альтернатива в Go
В современном Go часто используют каналы вместо Condition Variable:
// Вместо Condition Variable используем канал
ready := make(chan bool)
go func() {
<-ready // Ожидаем
fmt.Println("Готово!")
}()
go func() {
time.Sleep(1 * time.Second)
ready <- true // Сигнализируем
}()
Каналы часто предпочтительнее, так как более гибкие и безопаснее, но Condition Variable полезны для сложных сценариев Producer-Consumer с буферизацией.