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中文网其他相关文章!