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