Почему в следующем примере с условными переменными в качестве примитивов синхронизации возникает гонка данных?

В документации 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), поэтому не должно быть гонки данных.

Я знаю, как решить проблему гонки данных (чтобы избавиться от флага гонки данных) в этом примере (с помощью мьютекса в первой горутине), но мне любопытно, почему этот код (как есть) создает гонку данных. Как я писал, по документации этого быть не должно.

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
0
0
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Согласно документу:

Каждый Cond имеет связанный с ним Locker L (часто *Mutex или *RWMutex), который необходимо удерживать при изменении условия и при вызове метода Wait.

Таким образом:

m.Lock()
a = 20
m.Unlock()
c.Signal()

Ну да, но тогда документация о синхронизации перед отношением между Signal/Broadcast и Wait не имеет особого смысла. Здесь синхронизация достигается за счет синхронизации мьютекса Unlock/Lock перед отношением. Полная ерунда

Cosmos 22.07.2024 04:52

Гарантии модели памяти применяются только в том случае, если вы используете ее указанным образом. Там явно сказано, что вам нужно удерживать блокировку при изменении условия.

Burak Serdar 22.07.2024 05:27

Также обратите внимание, что в документации не рекомендуется использовать условные переменные. Почти все варианты использования условных переменных могут быть реализованы с помощью канала.

Burak Serdar 22.07.2024 05:28

Означает ли здесь «состояние» a?

ruakh 22.07.2024 06:06

Условие — установка a на 20.

Burak Serdar 22.07.2024 06:08

Другие вопросы по теме