tld*_*ldr 7 deep-copy go race-condition goroutine
在Go中防止种族状况有哪些好的做法?
我能想到的唯一一个就是不在goroutine之间共享数据 - 父goroutine发送一个对象的深层副本而不是对象本身,所以child goroutine不能改变父对象的内容.这将消耗更多的堆内存,但另一种方法是学习Haskell:P
编辑:同样,是否有任何情况我上面描述的方法仍然可以遇到竞争条件?
Rob*_*ier 14
即使使用非共享数据结构,竞争条件肯定仍然存在.考虑以下:
B asks A for the currentCount
C asks A for the currentCount
B sends A (newDataB, currentCount + 1)
A stores newDataB at location currentCount+1
C sends A (newDataC, currentCount + 1)
A stores newDataC at currentCount + 1 (overwriting newDataB; race condition)
Run Code Online (Sandbox Code Playgroud)
这种竞争条件要求A中的私有可变状态,但是没有可变的共享数据结构,甚至不需要B或C中的可变状态.如果不理解A提供的合同,B或C就无法阻止这种竞争条件.
一旦状态进入等式,即使是哈斯克尔也会遇到这种竞争条件,并且状态很难完全从实际系统中消除.最终你希望你的程序与现实互动,现实是有状态的.Wikipedia 使用STM 在Haskell中提供了一个有用的竞争条件示例.
我同意良好的不可变数据结构可以使事情变得更容易(Go并不真正拥有它们).可变副本将一个问题换成另一个问题.您不能无意中更改其他人的数据.另一方面,当您实际上只是更改副本时,您可能会认为您正在更改真实的副本,从而导致出现其他类型的错误.你必须以任何方式理解合同.
但最终,Go倾向于遵循C的并发历史:你为你的代码制定一些所有权规则(比如@ tux21b商品)并确保你总是遵循它们,如果你完美地完成它,那么一切都会很好,如果你犯了错误,那么显然这是你的错,而不是语言.
(不要误解我的意思;我非常喜欢Go.它提供了一些很好的工具来简化并发.它只是没有提供很多语言工具来帮助使并发性正确.这取决于你.这就是说. ,tux21b的答案提供了很多好的建议,而竞赛探测器绝对是减少竞争条件的有力工具.它不是语言的一部分,而是关于测试,而不是正确性;它们不是一回事.)
编辑:关于为什么不可变数据结构使事情变得容易的问题,这是你的初始点的扩展:创建一个多方不改变相同数据结构的契约.如果数据结构是不可变的,那么这是免费的......
许多语言都有丰富的不可变集合和类.C++可以让你做const任何事情.Objective-C具有可变子类的可变集合(它创建了一组不同的模式const).Scala具有许多集合类型的单独的可变和不可变版本,并且通常的做法是仅使用不可变版本.在方法签名中声明不变性是合同的重要标志.
当你传递[]byte给goroutine时,无法从代码中知道goroutine是否打算修改切片,也不能自己修改切片.有一种模式出现了,但它们就像移动语义之前的C++对象所有权一样; 很多很好的方法,但无法知道哪一个正在使用.每个程序都需要正确执行它是一件至关重要的事情,但是语言没有为您提供好的工具,开发人员也没有使用通用模式.
Go不会静态地强制执行内存安全.即使在大型代码库中,有几种方法可以解决这个问题,但所有这些方法都需要您的注意.
您可以发送指针,但一个常见的习惯用法是通过发送指针来表示所有权的转移.例如,一旦你将一个物体的指针传递给另一个Goroutine,你就不要再触摸它了,除非你通过另一个信号让物体从那个goroutine(或任何其他Goroutine,如果物体绕过几次)返回.
如果您的数据由许多用户共享并且不经常更改,则可以全局共享指向该数据的指针,并允许每个人从中读取数据.如果Goroutine想要改变它,它需要遵循写时复制的习惯用法,即复制对象,改变数据,尝试使用atomic.CompareAndSwap之类的东西设置指向新对象的指针.
使用Mutex(如果您想同时允许多个并发读取器,则使用RWMutex)并不是那么糟糕.当然,Mutex不是银弹,它通常不适合进行同步(并且它在许多语言中被过度使用导致其声誉不佳),但有时它是最简单和最有效的解决方案.
可能还有很多其他方法.仅通过复制它们发送值是另一个并且易于验证,但我认为您不应仅限于此方法.我们都很成熟,我们都能够阅读文档(假设你正确记录了你的代码).
Go工具还内置了一个非常有价值的竞赛检测器,可以在运行时检测比赛.编写大量测试并在启用竞争检测器的情况下执行它们,并认真对待每条错误消息.它们通常表示设计不良或复杂.
(PS:你可能想看一下Rust,如果你想要一个能够在编译期间验证并发访问的编译器和类型系统,同时仍然允许共享状态.我自己没有使用它,但这些想法看起来相当有希望.)