Состояние гонки с включенным мьютексом и куда встроить блокировку, родительскую структуру или дочернюю структуру?

  1. Я вижу, как некоторые люди используют несколько мьютексов в одной структуре. Это необходимо? Или мы можем просто поставить туда один-единственный замок? пример:
type Session struct {
    // some other irrelevant code
    pingLock sync.Mutex
    // some other irrelevant code
    streamLock sync.Mutex
    // some other irrelevant code
    shutdownLock sync.Mutex
}
  1. Если структура содержит другую структуру, а дочерняя структура содержит карту или срез, куда следует поместить мьютекс, родительскую структуру или дочернюю структуру?
  2. Почему mutex.Lock() не работает в моем коде? Каждый раз, когда я запускаю гоночный тест, он показывает, что гонки есть.

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

type Test struct {
    name string
    data Data
    sync.RWMutex //Should I put it here?
}

type Data struct {
    d map[string]int
    sync.RWMutex // Should I put it here?
}

func (t *Test) add(key string) {
    t.data.Lock()
    defer t.data.Unlock()
    t.data.d[key] += 1
}

func (t *Test) read() {
    for {
        t.data.Lock()
        _= t.data.d["test"]
        t.data.Unlock()
    }
}

func main() {

    t := &Test{}
    t.name = "oops"
    t.data = Data{}
    t.data.d = make(map[string]int)
    t.data.d["test"] = 1

    for i := 0; i <= 10; i++ {
        go func(t *Test) {
            t.add("test")
        }(t)
        go func(t *Test) {
            t.read()
        }(t)
    }
    time.Sleep(time.Second * 3)
    fmt.Printf("result is %v", t.data.d["test"])

person Randix Lai Randy    schedule 18.06.2019    source источник


Ответы (2)


Как упоминалось в @peterSO, ошибка вызвана "fmt.Printf("result is %v\n", t.data.d["test"])". После нескольких часов копания я, кажется, нашел ответы на свои первые два вопроса. Я неправильно понял концепцию мьютекса. Мьютекс используется для защиты ресурса, а не для блокировки самой памяти, в моем примере — самой структуры. На первый вопрос: если одна горутина выполняет какой-то код вроде

s.pingLock.Lock()
\\ some logic 1
s.pingLock.Unlock()
streamLock
\\ some logic 2
streamUnlock

Таким образом, когда одна горутина выполняет этот код и получает s.streamLock.Lock(), пока она не будет разблокирована, другие подпрограммы go не могут выполнить some logic 2, но любая горутина может выполнить some logic1, если она получит pingLock.Lock(). При использовании только одной блокировки, если одна процедура go получает блокировку, никто другой не может получить блокировку, тогда все остальные выполнения блокируются.

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

Я не уверен, что это правильно. Если у кого-то есть другое мнение или лучший ответ, пожалуйста, дайте мне знать.

person Randix Lai Randy    schedule 19.06.2019

Гоночный тест продолжает говорить, что есть состояние гонки.


Заявление

fmt.Printf("result is %v", t.data.d["test"])

передает аргумент

t.data.d["test"]

по стоимости. Он делает копию по назначению, которая является чтением.

Вам нужно защитить чтение через мьютекс.

t.data.Lock()
fmt.Printf("result is %v\n", t.data.d["test"])
t.data.Unlock()
person peterSO    schedule 18.06.2019
comment
Спасибо, что указали на это. Это исправляет вопрос. Я понимаю, что это чтение сейчас. Но почему важно передавать аргумент по значению или нет? И где я должен разместить свой мьютекс, в Test struct , Data Struct или в обоих? - person Randix Lai Randy; 19.06.2019
comment
Я обновил свой вопрос. Не могли бы вы помочь с этим? - person Randix Lai Randy; 19.06.2019