【问题标题】:Is sync.Mutex needed when accessing different fields of the same struct, but in separate goroutines?访问同一结构的不同字段但在不同的 goroutine 中是否需要 sync.Mutex?
【发布时间】:2020-04-11 08:13:40
【问题描述】:

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

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

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

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

【问题讨论】:

标签: go goroutine


【解决方案1】:

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

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)
}()

【讨论】:

    【解决方案2】:

    您必须非常小心使用 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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-10-08
      • 2014-06-17
      • 1970-01-01
      • 2017-10-20
      • 1970-01-01
      • 1970-01-01
      • 2021-05-02
      相关资源
      最近更新 更多