Что такое condition variable в синхронизации
Что проверяют
Знаете ли вы контракт примитива: как связаны мьютекс, предикат и ожидание. И отдельно, понимаете ли, что в Go это нишевый инструмент, а основной способ передавать сигнал между горутинами это каналы.
Как отвечать
Условная переменная это место ожидания события, всегда связанное с мьютексом. Ожидающая сторона берёт блокировку, проверяет предикат и, если он ложен, вызывает ожидание: блокировка атомарно отпускается, горутина засыпает, а при пробуждении блокировка берётся заново. Изменяющая сторона правит состояние под тем же мьютексом и будит одного ожидающего или всех сразу.
В стандартной библиотеке это sync.Cond, создаётся как sync.NewCond(&mu). Главное правило: предикат проверяется в цикле, а не одной проверкой.
mu.Lock()
for len(buf) == 0 {
cond.Wait()
}
item := buf[0]
buf = buf[1:]
mu.Unlock()Когда выбирать каналы, а когда условную переменную: каналы почти всегда, потому что условная переменная не умеет ни таймаута, ни отмены по контексту, и её нельзя положить в select. Она оправдана там, где ожидающих много, а состояние сложнее одного сообщения: пул с ограничением, буфер с несколькими условиями пробуждения.
Где спотыкаются
Проверяют условие один раз вместо цикла. Между пробуждением и повторным взятием блокировки состояние успевает измениться: другой ожидающий забрал элемент, и проснувшийся работает с пустым буфером. Цикл нужен всегда, независимо от того, кажется ли гонка возможной.
Меняют состояние без блокировки или будят ожидающих, не тронув предикат. Проснувшаяся горутина проверит условие, ничего нового не найдёт и заснёт снова, а сигнал уже потрачен и повторно не придёт. Отсюда зависания, которые воспроизводятся раз в сутки.
Будят одного там, где ожидающие ждут разных условий. Разбуженной оказывается горутина, которая продолжить не может, и система встаёт, хотя другая была готова работать. В таких схемах нужно широковещательное пробуждение. Сюда же относится копирование условной переменной после первого использования.