В документации Golang для условных переменных указано следующее:
В терминологии модели памяти Go Cond организует вызов для Broadcast или Signal «синхронизируется перед» любым вызовом Wait, который он разблокирует.
[ссылка]
Однако следующий код (запускаемый через go run -race .
) дает флаг DATA RACE:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
m := sync.Mutex{}
c := sync.NewCond(&m)
var a = 10
go func() {
time.Sleep(time.Second)
a = 20
c.Signal()
}()
go func() {
defer m.Unlock()
m.Lock()
c.Wait()
fmt.Println(a)
}()
select {}
}
Почему?
Вторая горутина попадает в Wait()
. Через некоторое время (одну секунду) первая горутина подает сигнал, вызывая Signal()
. Я использую time.Sleep здесь, чтобы гарантировать, что вторая горутина сначала попадет в режим ожидания до того, как произойдет сигнал (в противном случае вторая горутина попадет в тупик). Поскольку вызов Signal()
синхронизируется перед любым вызовом Wait()
, который разблокирует, я не понимаю, как может быть гонка данных между a = 20
и fmt.Println(a)
.
Поскольку a = 20
упорядочивается раньше c.Signal()
, который синхронизируется перед разблокировкой c.Wait()
, который упорядочивается раньше fmt.Println(a)
, результат a = 20
происходит раньше fmt.Println(a)
, поэтому не должно быть гонки данных.
Я знаю, как решить проблему гонки данных (чтобы избавиться от флага гонки данных) в этом примере (с помощью мьютекса в первой горутине), но мне любопытно, почему этот код (как есть) создает гонку данных. Как я писал, по документации этого быть не должно.
Согласно документу:
Каждый Cond имеет связанный с ним Locker L (часто *Mutex или *RWMutex), который необходимо удерживать при изменении условия и при вызове метода Wait.
Таким образом:
m.Lock()
a = 20
m.Unlock()
c.Signal()
Гарантии модели памяти применяются только в том случае, если вы используете ее указанным образом. Там явно сказано, что вам нужно удерживать блокировку при изменении условия.
Также обратите внимание, что в документации не рекомендуется использовать условные переменные. Почти все варианты использования условных переменных могут быть реализованы с помощью канала.
Означает ли здесь «состояние» a
?
Условие — установка a
на 20.
Ну да, но тогда документация о синхронизации перед отношением между
Signal
/Broadcast
иWait
не имеет особого смысла. Здесь синхронизация достигается за счет синхронизации мьютексаUnlock
/Lock
перед отношением. Полная ерунда