首页  >  文章  >  后端开发  >  为什么 Go 的 `sync.Once` 使用 `atomic.StoreUint32` 而不是普通的赋值来设置 `done` 标志?

为什么 Go 的 `sync.Once` 使用 `atomic.StoreUint32` 而不是普通的赋值来设置 `done` 标志?

Patricia Arquette
Patricia Arquette原创
2024-10-31 10:48:02904浏览

Why does Go's `sync.Once` use `atomic.StoreUint32` instead of normal assignment to set the `done` flag?

Go 的sync.Once 中原子操作的正确使用

在Go 的sync.Once 实现的上下文中,了解以下内容至关重要:设置done标志时普通赋值和atomic.StoreUint32操作之间的区别。

不正确的实现

最初,once.go中的Do函数使用了以下方法:

if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    f()
}

此实现无法保证 Do 返回时 f 的执行完成。对 Do 的两个并发调用可能会导致第一个调用成功调用 f,而第二个调用过早返回,认为 f 已完成,即使它尚未完成。

原子存储操作

为了解决这个问题,Go 使用了atomic.StoreUint32 操作。与普通赋值不同,atomic.StoreUint32 确保更新后的完成标志对其他 goroutine 的可见性。

内存模型注意事项

sync.Once 中原子操作的使用是主要不受底层机器的内存模型的影响。 Go 的内存模型充当统一的抽象,确保不同硬件平台之间的行为一致,无论其特定的内存模型如何。

优化的快速路径

要优化性能,请同步.Once 对于已设置完成标志的常见场景采用快速路径。此快速路径利用atomic.LoadUint32 来检查完成标志而不获取互斥体。如果设置了该标志,则该函数立即返回。

具有互斥锁和原子存储的慢速路径

当快速路径失败时(即最初未设置完成),进入慢速路径。获取互斥体以确保只有一个调用者可以继续执行 f。 f完成后,使用atomic.StoreUint32设置done标志,使其对其他goroutines可见。

并发读取

即使设置了done标志从原子角度来说,它并不保证并发读取的安全。读取受保护临界区之外的标志需要使用atomic.LoadUint32。然而,由于互斥体提供了互斥,直接读取临界区是安全的。

综上所述,Go 的sync.Once 利用atomic.StoreUint32 来确保done 标志的修改一致且可见,无论底层内存 モデル 并避免数据竞争。原子操作和互斥体的结合提供了性能优化和正确性保证。

以上是为什么 Go 的 `sync.Once` 使用 `atomic.StoreUint32` 而不是普通的赋值来设置 `done` 标志?的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn