问题 在go中访问struct的不同成员是否安全?


从不同的goroutines访问不同的struct成员是否安全?

我明白在没有同步的情况下写入同一个变量是很糟糕的:

package main

type Apple struct {
    color string
    size  uint
}

func main() {
    apple := &Apple{}
    go func() {
        apple.color = "red"
    }()
    go func() {
        apple.color = "green"
    }()
}

但是你可以在没有任何同步的情况下写入不同的struct成员吗?

package main

type Apple struct {
    color string
    size  uint
}

func main() {
    apple := &Apple{}
    go func() {
        apple.color = "red"
    }()
    go func() {
        apple.size = 42
    }()
}

或者我应该使用 chan 要么 sync.Mutex 为了那个原因?


1570
2018-04-07 16:35


起源



答案:


从不同的线程访问不同的变量应该是安全的,而struct成员是不同的变量。所以是的它应该是安全的。

但是,它可能不会很快。与结构成员一样在内存中靠近的变量将共享CPU缓存行。缓存行是CPU(嗯,大多数当前型号)可以锁定的最小内存。这意味着CPU-2必须等待写入,直到CPU-1完成该缓存行,即使它们正在写入不同的变量。

从不同的线程写入结构时,更改指向结构的指针是不安全的。在你的例子中,如果你有第三个goroutine apple = &Apple{} 其他线程中的其他一些goroutine可能会写给旧Apple或新Apple,你不会知道。


11
2018-04-07 17:12



第1和第3段很棒;第2段似乎偏离主题(因为关注是安全)和糟糕的建议(因为Go是如此高级的语言,你应该默认编写最具表现力的代码,并根据具体情况优化需要的地方 - 一般不应用这个理由。不能保证这些goroutine甚至可以由不同的CPU执行!)。 - weberc2
同意@ weberc2。虽然错误共享是一个问题,但问题是关于使用或不使用互斥锁修改2个变量。无论哪个答案是正确的,都存在缓存行问题,并且无论如何解决方案都是相同的。 - JimB
@ weberc2:好笑,因为我喜欢Go的原因之一是因为它不是那么高级的语言。它编译很像C与垃圾收集。 - Zan Lynx
第二段对于那些考虑更大问题的人有帮助:并发访问struct字段的问题是什么?我对最后一段感到担忧,因为它会将指针与结构本身混淆。可以修改结构,而其他一些goroutine恰好修改指向该结构的指针。 - ThunderCat
@KamilDziedzic Struct成员占据不同的内存段。如果从不同的线程更改不同的内存段,则不会发生冲突。我手边没有任何资源,但你可以谷歌'C struct memory layout'(或类似的东西)并获得更多信息(就我所知,Go的内存布局与C非常相似)。 - weberc2


答案:


从不同的线程访问不同的变量应该是安全的,而struct成员是不同的变量。所以是的它应该是安全的。

但是,它可能不会很快。与结构成员一样在内存中靠近的变量将共享CPU缓存行。缓存行是CPU(嗯,大多数当前型号)可以锁定的最小内存。这意味着CPU-2必须等待写入,直到CPU-1完成该缓存行,即使它们正在写入不同的变量。

从不同的线程写入结构时,更改指向结构的指针是不安全的。在你的例子中,如果你有第三个goroutine apple = &Apple{} 其他线程中的其他一些goroutine可能会写给旧Apple或新Apple,你不会知道。


11
2018-04-07 17:12



第1和第3段很棒;第2段似乎偏离主题(因为关注是安全)和糟糕的建议(因为Go是如此高级的语言,你应该默认编写最具表现力的代码,并根据具体情况优化需要的地方 - 一般不应用这个理由。不能保证这些goroutine甚至可以由不同的CPU执行!)。 - weberc2
同意@ weberc2。虽然错误共享是一个问题,但问题是关于使用或不使用互斥锁修改2个变量。无论哪个答案是正确的,都存在缓存行问题,并且无论如何解决方案都是相同的。 - JimB
@ weberc2:好笑,因为我喜欢Go的原因之一是因为它不是那么高级的语言。它编译很像C与垃圾收集。 - Zan Lynx
第二段对于那些考虑更大问题的人有帮助:并发访问struct字段的问题是什么?我对最后一段感到担忧,因为它会将指针与结构本身混淆。可以修改结构,而其他一些goroutine恰好修改指向该结构的指针。 - ThunderCat
@KamilDziedzic Struct成员占据不同的内存段。如果从不同的线程更改不同的内存段,则不会发生冲突。我手边没有任何资源,但你可以谷歌'C struct memory layout'(或类似的东西)并获得更多信息(就我所知,Go的内存布局与C非常相似)。 - weberc2