去原子加载和存储

时间:2014-08-27 05:37:38

标签: go raft

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?这是什么阻止了?

感谢。

2 个答案:

答案 0 :(得分:12)

原子功能以一种孤立的方式完成任务,任务的所有部分似乎都是即时发生的,或根本不发生。

在这种情况下,LoadInt32和StoreInt32确保以某种方式存储和检索整数,其中某人加载不会获得部分存储。但是,您需要双方使用原子函数才能正常运行。由于至少两个原因,raft示例显示不正确。

  1. 两个原子函数不作为一个原子函数,因此读取旧函数并将两个行中的新函数设置为竞争条件。您可以阅读,然后其他人设置,然后进行设置,并在设置之前返回上一个值的虚假信息。

  2. 并非所有访问MinimumElectionTimeoutMS的人都在使用原子操作。这意味着在这个函数中使用原子实际上是无用的。

  3. 如何解决这个问题?

    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.goRaft 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

请参阅: