Ответ
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для оповещения многих.- Каналы: Лучше подходят для прямой коммуникации между горутинами, передачи владения данными или для простой синхронизации (сигнал "готово").
Ответ 18+ 🔞
А, ну вот, блядь, sync.Cond! Это ж такая штука, когда горутине надо не просто мьютекс отжать, а дождаться, пока какое-то условие, блядь, выполнится. Типа она сидит и думает: «Ну когда же, сука, в этой очереди хоть что-то появится?» А пока пусто — спать, блядь!
Представь, что у тебя есть очередь, охраняемая мьютексом. И сидят две голодные горутины-потребителя, как пиздюки у холодильника, ждут, пока мамка (то есть продюсер) туда сэндвич не положит. Без Cond им пришлось бы в бесконечном цикле дёргать мьютекс: «Есть чё? Не-а. Есть чё? Не-а». Это же пиздец, CPU сожрут, как мартышки бананы!
А с Cond — красота. Сказал Wait() — и уснул, блядь, как сурок. А когда продюсер добавит элемент и крикнет Signal() — одна из спящих горутин просыпается, хватает мьютекс обратно и проверяет: «А чё там, реально есть?». Если да — хавает и довольна. Если нет (ложное пробуждение бывает, ёпта!) — снова спать.
Вот, смотри, как это выглядит в коде, блядь:
var (
mu sync.Mutex
cond = sync.NewCond(&mu) // Cond всегда привязан к мьютексу, это закон!
queue []int
)
// Потребитель — голодный чувак
func consumer(id int) {
mu.Lock()
// ВАЖНО: проверяем условие в цикле, а не в if! Иначе проснёшься, а там нихуя.
for len(queue) == 0 {
fmt.Printf("Потребитель %d: ну чё, пусто... буду спать.n", id)
cond.Wait() // 1. Отпускает мьютекс. 2. Засыпает. 3. Проснувшись, снова лочит мьютекс.
}
// Опа, чё-то есть!
item := queue[0]
queue = queue[1:]
fmt.Printf("Потребитель %d схавал %dn", id, item)
mu.Unlock()
}
// Производитель — наш спаситель
func producer() {
time.Sleep(2 * time.Second) // Имитируем, блядь, тяжёлую работу
mu.Lock()
queue = append(queue, 1)
fmt.Println("Производитель: кладу сэндвич! Буду одного!")
cond.Signal() // Будит ОДНУ случайную спящую горутину. Как будто крикнул: «Кушать подано!»
// cond.Broadcast() // А это если крикнуть: «ВСЕМ СЮДА, ЕБА!» — проснутся все.
mu.Unlock()
}
Главные правила, блядь, чтобы не обосраться:
Wait()— только в цикле. Всегда.for !condition { cond.Wait() }. Иначе проснёшься от ложного сигнала, а условие не выполнилось — и ты пошёл работать с пустой очередью. Пиздец и падение.- Мьютекс должен быть залочен перед вызовом
Wait(),Signal()илиBroadcast(). СамCondза этим не следит, это твоя ответственность, чувак. Signal()илиBroadcast()?Signal()— как пнуть одного спящего соседа. «Эй, проснись, там чё-то есть». Используй, когда разбудить достаточно кого-то одного (как с очередью).Broadcast()— это уже «ПОЖАР, БЛЯДЬ, ВСЕ ВОН!». Будит всех. Нужно, когда событие касается всех ждущих (например, глобальная конфигурация поменялась, и всем надо её перечитать).
А когда использовать каналы, а когда этот твой Cond?
sync.Cond— это когда у тебя есть, блядь, разделяемое состояние (очередь, флаг, счётчик), охраняемое мьютексом, и горутины ждут его изменения. Особенно если ждущих много и их надо будить пачками черезBroadcast. Это низкоуровневый, но мощный инструмент.- Каналы — это для коммуникации, передачи данных и простых сигналов «готово». «На, получи пачку данных и работай». Более идиоматично для Go в большинстве случаев.
Короче, Cond — это как дежурный по казарме. Все спят, а он орёт: «Подъём! Условие выполнилось!». Только не забудь проверить, не обманул ли он тебя, этот ёбаный дежурный. Всегда проверяй условие в цикле!