После гарантированно выполненного Signal программа всё равно блокируется на Wait. Какой механизм sync.Cond это объясняет?
package main
import "sync"
func main() {
cond := sync.NewCond(&sync.Mutex{})
done := make(chan struct{})
go func() {
cond.L.Lock()
cond.Signal()
cond.L.Unlock()
close(done)
}()
<-done
cond.L.Lock()
cond.Wait()
cond.L.Unlock()
}
sync.Cond.Signal не сохраняет уведомление для будущих ожидающих горутин. Если в момент вызова Signal никто не находится внутри Wait, сигнал теряется, поэтому последующий Wait блокируется навсегда.
Правильный шаблон — защищать общую переменную мьютексом и ждать не сам сигнал, а изменение условия в цикле for. Сигнал лишь побуждает повторно проверить это условие.
sync.Cond реализует классический примитив условной переменной: горутина временно освобождает мьютекс и засыпает, пока состояние, защищаемое этим мьютексом, не станет подходящим. Такой подход решает проблему постоянного опроса общего состояния с задержками и лишней нагрузкой на процессор.
В отличие от канала, sync.Cond не предназначен для передачи значений или накопления уведомлений. Он связывает ожидание с внешним предикатом, например доступностью места в очереди или переходом состояния объекта.
В примере горутина вызывает Signal, когда ожидающих ещё нет. После этого другая горутина вызывает Wait, но уже не существует события, которое могло бы её разбудить.
Главная ошибка — ожидание уведомления вместо проверки состояния. Даже если заменить порядок операций, одиночный вызов Wait ненадёжен: горутину могут разбудить ложное пробуждение или другой поток, хотя требуемое условие ещё не выполнено.
Wait должен вызываться при удержании мьютекса. Внутри он атомарно добавляет вызывающую горутину в список ожидающих и освобождает мьютекс; после пробуждения горутина снова захватывает мьютекс и только затем возвращается из Wait.
Состояние проверяют в цикле:
Изменение ready и его проверка защищены одним мьютексом. Broadcast будит всех текущих ожидающих, а Signal — одного; ни один из них не запоминает уведомление для будущих ожидателей.
Цикл for обязателен: после пробуждения условие могло уже быть изменено другой горутиной. Кроме того, документация допускает возврат из Wait без гарантии, что ожидаемое состояние стало истинным.
Если требуется передавать данные, сигнализировать о закрытии или поддерживать отмену, канал часто проще и безопаснее. sync.Cond уместен, когда есть сложное общее состояние под мьютексом и уведомление не является самостоятельным сообщением.
В планировщике задач есть очередь, защищённая мьютексом. Рабочие горутины должны спать, пока очередь пуста, а добавление задачи должно будить ожидающих.
Канал хорошо подходит, если каждая задача передаётся как отдельное значение: отправка одновременно уведомляет и передаёт данные. Однако при сложных операциях над общей очередью, нескольких условиях и необходимости атомарно проверить несколько полей код на каналах может стать менее прозрачным.
Выбранный вариант — очередь под sync.Mutex, ожидание for len(queue) == 0 { cond.Wait() } и Signal или Broadcast после добавления задач. Это не теряет событие, потому что корректность определяется содержимым очереди, а не фактом получения сигнала; результатом становится отсутствие зависаний при гонках между добавлением и ожиданием.
Wait без предварительной проверки условия?Нет. Wait не проверяет бизнес-условие автоматически и не является ожиданием конкретного события. Вызывающий код обязан захватить мьютекс, проверить предикат и войти в Wait только при его ложности.
Signal и Broadcast при нескольких ожидающих горутинах?Signal будит максимум одну текущую ожидающую горутину, а Broadcast — всех текущих ожидающих. После пробуждения каждая горутина должна заново захватить мьютекс и проверить предикат, поэтому даже после Broadcast часть горутин может снова уснуть.
Unlock, а затем вызвать Signal?Так возникает окно гонки: ожидающая горутина может проверить старое значение и заснуть до изменения состояния или уведомления. Предикат изменяют под тем же мьютексом, затем под его защитой вызывают Signal или Broadcast, и только после этого освобождают мьютекс.