有哪些好的实践可以在Go中防止竞态条件?
我所知道的唯一方法是不在goroutine之间共享数据-父goroutine发送对象的深拷贝而不是对象本身,这样子goroutine就无法改变父goroutine可操作的东西。这将使用更多的堆内存,但另一种选择是学习Haskell:P
编辑:还有,是否存在任何场景,即使使用我上面描述的方法仍可能遇到竞态条件?
有哪些好的实践可以在Go中防止竞态条件?
我所知道的唯一方法是不在goroutine之间共享数据-父goroutine发送对象的深拷贝而不是对象本身,这样子goroutine就无法改变父goroutine可操作的东西。这将使用更多的堆内存,但另一种选择是学习Haskell:P
编辑:还有,是否存在任何场景,即使使用我上面描述的方法仍可能遇到竞态条件?
即使使用未共享的数据结构,竞态条件仍然可能存在。请考虑以下情况:
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可以做来防止这种竞争条件,除非他们理解A提供的约定。
即使是Haskell只要涉及状态就可能遭受这些类型的竞争条件,而从现实系统中完全消除状态非常困难。最终您希望程序与现实交互,并且现实是有状态的。Wikipedia给出了使用STM的Haskell竞争条件示例。
我同意好的不可变数据结构可以使事情变得更容易(Go实际上没有这样的数据结构)。可变副本会把一个问题换成另一个问题。您无法意外更改其他人的数据。另一方面,您可能会认为正在更改真实的数据,而实际上只是更改了副本,导致不同类型的错误。您必须以任何方式都要理解约定。
但最终,Go倾向于跟随C的并发历史:您为代码制定一些所有权规则(例如@tux21b提供的规则),并确保您始终遵循它们。如果您完美地做到了这一点,一切都会很好。如果您犯了一个错误,那显然是您的错,而不是语言的错。
(别误会我;我很喜欢Go。它提供了一些很好的工具,使并发变得容易。但它没有提供太多的语言工具来帮助实现并发的正确性。这取决于您。尽管如此,tux21b的答案提供了许多好的建议,竞争检测器确实是减少竞争条件的有力工具,只是它不是语言的一部分,而是关于测试的,而不是正确性。它们并不相同。)
编辑:关于为什么不可变数据结构可以使事情变得更容易的问题,这是你最初观点的延伸:创建一个约定,多个方不更改同一数据结构。如果数据结构是不可变的,则可以轻松实现该约定...
许多语言都拥有丰富的不可变集合和类。C++允许您const
几乎任何东西。Objective-C具有具有可变子类的不可变集合(这创建了与const
不同的模式)。Scala针对许多集合类型有单独的可变和不可变版本,并且使用不可变版本是常见做法。在方法签名中声明不可变性是约定的重要指示。
当您将[]byte
传递给协程时,无法从代码中知道协程是否打算修改该片段,也不知道何时可以自己修改该片段。此时出现了一些模式,但它们就像移动语义之前的C++对象所有权一样;有很多好的方法,但没有办法知道哪种方法正在使用。这是每个程序都需要正确执行的关键事情,然而语言并没有给您提供好的工具,并且开发人员也没有普遍采用通用模式。
n log n
空间(或某些潜在情况下更少)上完成,而不是天真的2n
。虽然语言/优化器支持可以为专门构建的语言做一些疯狂的事情... - Clockwork-Muse