func resetElectionTimeoutMS(newMin, newMax int) (int, int) {
oldMin := atomic.LoadInt32(&MinimumElectionTimeoutMS)
oldMax := atomic.LoadInt32(&maximumElectionTimeoutMS)
atomic.StoreInt32(&MinimumElectionTimeoutMS, int32(newMin))
atomic.StoreInt32(&maximumElectionTimeoutMS, int32(newMax))
return int(oldMin), int(oldMax)
}
我得到了这样的代码函数。
我感到困惑的是:为什么我们需要atomic
?这是什么阻止了?
感谢。
答案 0 :(得分:12)
原子功能以一种孤立的方式完成任务,任务的所有部分似乎都是即时发生的,或根本不发生。
在这种情况下,LoadInt32和StoreInt32确保以某种方式存储和检索整数,其中某人加载不会获得部分存储。但是,您需要双方使用原子函数才能正常运行。由于至少两个原因,raft示例显示不正确。
两个原子函数不作为一个原子函数,因此读取旧函数并将两个行中的新函数设置为竞争条件。您可以阅读,然后其他人设置,然后进行设置,并在设置之前返回上一个值的虚假信息。
并非所有访问MinimumElectionTimeoutMS的人都在使用原子操作。这意味着在这个函数中使用原子实际上是无用的。
如何解决这个问题?
func resetElectionTimeoutMS(newMin, newMax int) (int, int) {
oldMin := atomic.SwapInt32(&MinimumElectionTimeoutMS, int32(newMin))
oldMax := atomic.SwapInt32(&maximumElectionTimeoutMS, int32(newMax))
return int(oldMin), int(oldMax)
}
这将确保oldMin是交换之前存在的最小值。但是,整个函数仍然不是原子函数,因为最终结果可能是一个从未使用resetElectionTimeoutMS调用的oldMin和oldMax对。为此...只需使用锁。
还需要更改每个函数以执行原子加载:
func minimumElectionTimeout() time.Duration {
min := atomic.LoadInt32(&MinimumElectionTimeoutMS)
return time.Duration(min) * time.Millisecond
}
我建议你仔细考虑golang原子文档中提到的VonC引用:
这些功能需要非常小心才能正确使用。除了特殊的低级应用程序,最好使用通道或同步包的功能来实现同步。
如果您想了解原子操作,我建议您从http://preshing.com/20130618/atomic-vs-non-atomic-operations/开始。这将覆盖您的示例中使用的加载和存储操作。然而,原子还有其他用途。 go atomic package overview经历了一些很酷的事情,比如原子交换(我给出的例子),比较和交换(称为CAS),以及添加。
我给你的链接中有趣的引用:
众所周知,在x86上,如果内存操作数是自然对齐的,则32位mov指令是原子的,否则是非原子的。换句话说,只有当32位整数位于4的整数倍的地址时才能保证原子性。
换句话说,在今天的常见系统中,您的示例中使用的原子函数实际上是无操作。它们已经是原子的! (但不保证它们,如果你需要它是原子的,最好明确指定它)
答案 1 :(得分:1)
考虑到package atomic
提供了用于实现同步算法的低级原子内存原语,我想它本打算用作:
MinimumElectionTimeoutMS
中时,oldMin
未被修改
MinimumElectionTimeoutMS
在设置为新值newMin
时未被修改。但是,该软件包确实带有警告:
这些功能需要非常小心才能正确使用 除了特殊的低级别应用程序,最好使用通道或同步包的功能来实现同步 通过沟通分享记忆;不要通过共享记忆来沟通。
在这种情况下(来自server.go
的Raft distributed consensus protocol),直接在变量上进行同步可能比在{all}函数上放置Mutex
更快。
除了Stephen Weinberg的答案说明(upvoted)之外,这不是你如何使用原子。它只会确保oldMin
在进行交换时准确无误。
请参阅“Is the two atomic style code in sync/atomic.once.go
necessary?”中的另一个示例,与“ memory model ”相关。
OneOfOne使用原子CAS作为自旋锁(非常快速锁定)提到in the comments:
BenchmarkSpinL-8 2000 708494 ns/op 32315 B/op 2001 allocs/op
BenchmarkMutex-8 1000 1225260 ns/op 78027 B/op 2259 allocs/op
请参阅: