Что такое `sync.Cond` и для чего он используется? Приведите пример и объясните ключевые моменты его работы.

Ответ

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)
}

Ключевые правила использования:

  1. Wait() всегда в цикле: Горутина может быть разбужена ложно (spurious wakeup). Поэтому после пробуждения всегда нужно заново проверять условие (for !condition { cond.Wait() }).
  2. Блокировка мьютекса: Мьютекс должен быть заблокирован перед вызовом Wait(), Signal() или Broadcast().
  3. Signal() vs Broadcast():
    • 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()
}

Главные правила, блядь, чтобы не обосраться:

  1. Wait() — только в цикле. Всегда. for !condition { cond.Wait() }. Иначе проснёшься от ложного сигнала, а условие не выполнилось — и ты пошёл работать с пустой очередью. Пиздец и падение.
  2. Мьютекс должен быть залочен перед вызовом Wait(), Signal() или Broadcast(). Сам Cond за этим не следит, это твоя ответственность, чувак.
  3. Signal() или Broadcast()?
    • Signal() — как пнуть одного спящего соседа. «Эй, проснись, там чё-то есть». Используй, когда разбудить достаточно кого-то одного (как с очередью).
    • Broadcast() — это уже «ПОЖАР, БЛЯДЬ, ВСЕ ВОН!». Будит всех. Нужно, когда событие касается всех ждущих (например, глобальная конфигурация поменялась, и всем надо её перечитать).

А когда использовать каналы, а когда этот твой Cond?

  • sync.Cond — это когда у тебя есть, блядь, разделяемое состояние (очередь, флаг, счётчик), охраняемое мьютексом, и горутины ждут его изменения. Особенно если ждущих много и их надо будить пачками через Broadcast. Это низкоуровневый, но мощный инструмент.
  • Каналы — это для коммуникации, передачи данных и простых сигналов «готово». «На, получи пачку данных и работай». Более идиоматично для Go в большинстве случаев.

Короче, Cond — это как дежурный по казарме. Все спят, а он орёт: «Подъём! Условие выполнилось!». Только не забудь проверить, не обманул ли он тебя, этот ёбаный дежурный. Всегда проверяй условие в цикле!