Я прочитал необычный случай в документации sync.Cond:
Поскольку c.L не заблокирован во время ожидания, вызывающий обычно не может предполагать, что условие истинно, когда ожидание возвращается. Вместо, вызывающий должен ждать в цикле:
c.L.Lock()
for !condition() {
c.Wait()
}
... make use of condition ...
c.L.Unlock()
Я не понимаю идею этого... Например, в моем коде мне нужен цикл или нет?
func subscribe(name string, data map[string]string, c *sync.Cond) {
c.L.Lock()
for len(data) != 0 {
c.Wait()
}
fmt.Printf("[%s] %s\n", name, data["key"])
c.L.Unlock()
}
func publish(name string, data map[string]string, c *sync.Cond) {
time.Sleep(time.Second)
c.L.Lock()
data["key"] = "value"
c.L.Unlock()
fmt.Printf("[%s] data publisher\n", name)
c.Broadcast()
}
func main() {
data := map[string]string{}
cond := sync.NewCond(&sync.Mutex{})
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
subscribe("subscriber_1", data, cond)
}()
go func() {
defer wg.Done()
subscribe("subscriber_2", data, cond)
}()
go func() {
defer wg.Done()
publish("publisher", data, cond)
}()
wg.Wait()
}

Это не очевидно, но если вы посмотрите исходный код Cond.Wait, то увидите это:
c.checker.check()
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
Как видите, он разблокирует шкафчик (c.L , в вашем случае это sync.WaitGroup{}), а затем ждет уведомления ( runtime_notifyListWait(&c.notify, t)), а затем снова блокирует его.
Таким образом, ваши данные (условие) могут быть изменены в какой-либо другой горутине между c.L.Unlock() и c.L.Lock. Поэтому в документации предлагается проверять, изменилось ли условие (данные) или нет.
Поскольку c.L не заблокирован во время ожидания, вызывающий обычно не может предполагать, что условие истинно, когда ожидание возвращается. Вместо, вызывающий должен ждать в цикле:
Когда вы вызываете Wait() на sync.Cond, он освобождает соответствующую блокировку и приостанавливает выполнение вызывающей горутины до тех пор, пока другая горутина не вызовет Signal или Broadcast на том же sync.Cond.
Когда ожидание возвращается (это означает, что ему было сообщено), оно повторно захватывает блокировку, прежде чем вернуться к вызывающей стороне. Однако, поскольку блокировка была снята во время ожидания Wait, возможно, что условие, которого ждал вызывающий объект, изменилось между моментом вызова Wait и моментом возврата.
Например, предположим, что у вас есть общий буфер, и несколько горутин читают и пишут в него. Вы можете использовать sync.Cond, чтобы дождаться, пока буфер не станет пустым:
c.L.Lock()
for len(buffer) == 0 {
c.Wait()
}
// ... read from buffer ...
c.L.Unlock()
Вы вызываете Wait, когда буфер пуст, ожидая, что он вернется, как только другая горутина добавит элемент в буфер. Однако возможно, что после возврата Wait и до того, как ваша горутина снова начнет выполняться, другая горутина могла уже использовать элемент из буфера, снова оставив его пустым.
Из-за этого, когда Wait возвращается, вы не можете предположить, что len(buffer) != 0, хотя это то условие, которого вы ждали. Вместо этого вы должны снова проверить условие в цикле, как показано в примере кода, чтобы убедиться, что оно по-прежнему верно, прежде чем продолжить.
Цикл (for len(buffer) == 0) гарантирует, что если условие не будет выполнено, когда Wait вернется, он просто снова будет ждать, пока условие не будет выполнено.
Например, в моем коде мне нужен цикл или нет?
Да, ваша функция подписки должна иметь цикл.
В вашем случае вы ждете, пока длина карты данных не станет равной нулю. Однако ваша функция publisher добавляет элемент на карту, поэтому условие len(data) != 0 всегда будет истинным. Это приведет к тому, что функция Wait никогда не сработает.
Однако, если вы проверяли условие, которое могло быть обновлено несколько раз, цикл был бы необходим. Когда вызывается Wait, он снимает блокировку и приостанавливает выполнение вызывающей горутины. Позже, когда другая горутина вызывает Broadcast или Signal, вызов Wait возвращается и повторно получает блокировку. В этот момент условие, которого ждала горутина, может больше не выполняться, поэтому обычно следует вызывать Wait в цикле.
Короче говоря, в текущем состоянии вашего кода цикл не нужен, потому что ваше условие (длина карты данных становится равной нулю) никогда не будет выполняться на основе кода. Но если вы измените условие или добавите больше логики, может потребоваться использование цикла.
Если вы измените свою функцию издателя, чтобы очистить карту, и хотите, чтобы подписчик обрабатывал данные только тогда, когда карта пуста, вы должны использовать цикл следующим образом:
func subscribe(name string, data map[string]string, c *sync.Cond) {
c.L.Lock()
for len(data) != 0 {
c.Wait()
}
// ... process data ...
c.L.Unlock()
}
Этот цикл гарантирует, что обработка данных не начнется, пока карта не станет пустой. Он будет продолжать ждать, если карта не пуста всякий раз, когда ожидание возвращается.
Да, у вас должен быть цикл, но я думаю, что ваше условие цикла неверно. Как бы то ни было, вы выйдете из цикла только тогда, когда карта будет пустой.