gpt4 book ai didi

go - 为什么 atomic.StoreUint32 优于 sync.Once 中的正常分配?

转载 作者:行者123 更新时间:2023-12-03 10:06:39 25 4
gpt4 key购买 nike

在阅读Go的源代码时,我对src/sync/once.go中的代码有疑问:

func (o *Once) Do(f func()) {
// Note: Here is an incorrect implementation of Do:
//
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }
//
// Do guarantees that when it returns, f has finished.
// This implementation would not implement that guarantee:
// given two simultaneous calls, the winner of the cas would
// call f, and the second would return immediately, without
// waiting for the first's call to f to complete.
// This is why the slow path falls back to a mutex, and why
// the atomic.StoreUint32 must be delayed until after f returns.

if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}

func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}

为什么使用 atomic.StoreUint32,而不是 o.done = 1?这些不是等价的吗?有什么区别?

我们是否必须使用原子操作(atomic.StoreUint32)来确保其他 goroutines 可以在 o.done< 之前观察到 f() 的效果 在内存模型较弱的机器上设置为 1?

最佳答案

请记住,除非您手动编写程序集,否则您不是在针对机器的内存模型进行编程,而是针对 Go 的内存模型进行编程。这意味着即使原始分配对于您的体系结构是原子的,Go 也需要使用原子包来确保所有支持的体系结构的正确性。

访问互斥量之外的done标志只需要安全,不需要严格排序,因此可以使用原子操作而不是总是用互斥量获得锁。这是一种使快速路径尽可能高效的优化,允许在热路径中使用 sync.Once

用于 doSlow 的互斥量仅用于该函数内的互斥,以确保只有一个调用者在 done 之前到达 f() 标志已设置。该标志是使用 atomic.StoreUint32 编写的,因为它可能与 atomic.LoadUint32 同时发生在受互斥锁保护的临界区之外。

在写入的同时读取done 字段,即使是原子写入,也是一种数据竞争。仅仅因为该字段是原子读取的,并不意味着您可以使用正常赋值来写入它,因此首先使用 atomic.LoadUint32 检查标志并使用 atomic.StoreUint32 写入

doSlow 中直接读取 done 是安全的,因为互斥锁保护它免受并发写入。使用 atomic.LoadUint32 同时读取值是安全的,因为两者都是读取操作。

关于go - 为什么 atomic.StoreUint32 优于 sync.Once 中的正常分配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55964014/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com