在Go的sync.Once中正确使用原子操作
在Go的sync.Once实现的背景下,理解设置完成标志时正常赋值和atomic.StoreUint32操作之间的区别。
不正确实现
最初,once.go 中的 Do 函数使用了以下方法:
if atomic.CompareAndSwapUint32(&o.done, 0, 1) { f() }
此实现无法保证在 Do 返回时 f 的执行完成。对 Do 的两个并发调用可能会导致第一个调用成功调用 f,而第二个调用过早返回,认为 f 已完成,即使它尚未完成。
原子存储操作
为了解决这个问题,Go 采用了atomic.StoreUint32 操作。与普通赋值不同,atomic.StoreUint32 确保更新后的done标志对其他goroutine的可见性。
内存模型注意事项
sync.Once中原子操作的使用是主要不受底层机器的内存模型的影响。 Go 的内存模型充当统一的抽象,确保不同硬件平台之间的行为一致,无论其特定的内存模型如何。
优化的快速路径
要优化性能,请同步.Once 对于已设置完成标志的常见场景采用快速路径。此快速路径利用atomic.LoadUint32 来检查完成标志而不获取互斥体。如果设置了该标志,该函数立即返回。
具有互斥锁和原子存储的慢速路径
当快速路径失败时(即,最初未设置完成),进入慢速路径。获取互斥体以确保只有一个调用者可以继续执行 f。 f完成后,使用atomic.StoreUint32设置done标志,使其对其他goroutine可见。
并发读取
即使设置了done标志从原子角度来说,它并不保证并发读取的安全。读取受保护临界区之外的标志需要使用atomic.LoadUint32。然而,由于互斥体提供了互斥,直接读取临界区是安全的。
总而言之,Go 的sync.Once 利用atomic.StoreUint32 来确保done 标志的修改一致且可见,无论底层内存 モデル 并避免数据竞争。原子操作和互斥体的组合提供了性能优化和正确性保证。
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3