sync.Mutex 在访问同一结构的不同字段时是否需要,但在单独的 goroutine 中?

Is sync.Mutex needed when accessing different fields of the same struct, but in separate goroutines?

我有一个结构,其中有几个简单的字段(即:int、string 和 []byte)。

我还有几个 goroutines 修改结构中的不同字段。但是每个goroutine修改自己的字段。

我没有注意到竞争条件的任何问题或提示。然后我很容易地将 sync.Mutex 添加到代码中,并且代码 运行 完全相同。

但根据我的阅读,似乎有些人可能建议在这种情况下使用 sync.Mutex。是否需要,即使程序运行没有错误?

出于锁定的目的,将结构的每个字段视为独立的。给定:

type Foo struct {
    stringMap  map[string]string
    strings   []string
    numbers   []float64
}

以下是安全的:

foo := &Foo{}
/* ... */
go func() {
    foo.stringMap["foo"] = "bar"
}()
go func() {
    foo.strings[0] = "baz"
}()
go func() {
    fmt.Println(foo.numbers[0])
}()

但这是不安全的:

go func() {
    fmt.Println(foo.numbers[0])
}()
go func() {
    foo.numbers = append(foo.numbers, 123.456)
}()

因此您通常希望每个字段或每组可以一起修改的字段有一个互斥量:

type Foo struct {
    stringMapMu sync.Mutex
    stringMap   map[string]string
    stringsMu   sync.Mutex
    strings     []string
    numbersMu   sync.Mutex
    numbers     []float64
}

然后:

go func() {
    foo.numbersMu.Lock()
    defer foo.numbersMu.Unlock()
    fmt.Println(foo.numbers[0])
}()
go func() {
    foo.numbersMu.Lock()
    defer foo.numbersMu.Unlock()
    foo.numbers = append(foo.numbers, 123.456)
}()

Flimzys 方法您必须非常小心!如果你想在同一个 goroutine 中处理结构的不同字段并且不跟踪字段的阻塞顺序,你可能会陷入僵局。 https://play.golang.org/p/wbgs40UoP-h

相同的阻塞顺序可以正常工作

//the same order stringMapMu-->numbersMu
go func() {
    defer wg.Done()
    foo.stringMapMu.Lock()//lock on stringMap, waits unlocking numbersMu by other goroutine
    defer foo.stringMapMu.Unlock()

    foo.numbersMu.Lock()
    defer foo.numbersMu.Unlock()

    fmt.Println(foo.numbers, foo.stringMap)
}()

//works fine
//the same order stringMapMu-->numbersMu
go func() {
    defer wg.Done()

    foo.stringMapMu.Lock()//lock on stringMap, waits unlocking numbersMu by other goroutine
    defer foo.stringMapMu.Unlock()

    foo.numbersMu.Lock()
    defer foo.numbersMu.Unlock()

    foo.numbers = append(foo.numbers, 123.456)
    foo.stringMap["foo"] = "bar"
}()

但是相反的顺序是...死锁

    //reverse order numbersMu-->stringMapMu
go func() {
    foo.numbersMu.Lock()//lock on numbersMu, waits unlocking stringMapMu by other goroutine
    defer foo.numbersMu.Unlock()

    foo.stringMapMu.Lock()
    defer foo.stringMapMu.Unlock()

    foo.numbers = append(foo.numbers, 123.456)
    foo.stringMap["foo"]="bar"
}()

此外,各个字段的锁可以隐含地隐藏在“Set”和“Get”方法中。你将在你的 goroutine 中以不同的顺序操作它们,你最终会遇到死锁 =(如果这不是性能问题,我更希望你的结构有一个 Mutex。