为什么在sync.Once中,atomic.StoreUint32比普通分配更可取? [英] Why is atomic.StoreUint32 preferred over a normal assignment in sync.Once?
问题描述
在阅读Go的源代码时,我对src/sync/once.go中的代码有疑问:
While reading the source codes of Go, I have a question about the code in 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()
}
}
为什么使用 ataomic.StoreUint32
,而不是说 o.done = 1
?这些不相等吗?有什么区别?
why is ataomic.StoreUint32
used, rather than, say o.done = 1
? Are these not equivalent? What are the differences?
必须使用原子操作( atomic.StoreUint32
)确保其他goroutine可以观察到"f()"的影响.在内存模型较弱的机器上观察到将o.done设置为1之前是什么?
Must we use the atomic operation (atomic.StoreUint32
) to make sure that other goroutines can observe the effect of "f()" before o.done is set to 1 is observed on a machine with weak memory model?
推荐答案
请记住,除非您是手工编写程序集,否则您不是在对计算机的内存模型进行编程,而是在对Go的内存模型进行编程.这意味着即使您的体系结构中的原始分配都是原子的,Go仍需要使用原子包来确保所有受支持的体系结构的正确性.
Remember, unless you are writing the assembly by hand, you are not programming to your machine's memory model, you are programming to Go's memory model. This means that even if primitive assignments are atomic with your architecture, Go requires the use of the atomic package to ensure correctness across alls supported architectures.
访问互斥锁之外的 done
标志仅是安全的,没有严格的顺序,因此可以使用原子操作,而不必始终使用互斥锁获得锁.这是一项优化,旨在使快速路径尽可能高效,从而允许在热路径中使用 sync.Once
.
Access to the done
flag outside of the mutex only needs to be safe, not strictly ordered, so atomic operations can be used instead of always obtaining a lock with a mutex. This is an optimization to make the fast path as efficient as possible, allowing sync.Once
to be used in hot paths.
用于 doSlow
的互斥锁仅可在该函数内互斥,以确保只有一个调用者在 done完成之前进入
f()
标志已设置.该标记是使用 atomic.StoreUint32
编写的,因为它可能与 atomic.LoadUint32
并发发生在互斥锁保护的关键部分之外.
The mutex used for doSlow
is for mutual exclusion within that function alone, to ensure that only one caller ever makes it to f()
before the done
flag is set. The flag is written using atomic.StoreUint32
, because it may happen concurrently with atomic.LoadUint32
outside of the critical section protected by the mutex.
与写操作(甚至是原子写操作)同时读取 done
字段是一场数据竞赛.仅仅因为该字段是原子读取的,并不意味着您可以使用常规分配来编写该字段,因此首先使用 atomic.LoadUint32
检查该标志,然后使用 atomic.StoreUint32
编写该标志.
Reading the done
field concurrently with writes, even atomic writes, is a data race. Just because the field is read atomically, does not mean you can use normal assignment to write it, hence the flag is checked first with atomic.LoadUint32
and written with atomic.StoreUint32
直接在 doSlow
中完成的 done
的读取是安全的,因为互斥锁可以防止它被并发写入.与 atomic.LoadUint32
并发读取值是安全的,因为两者都是读取操作.
The direct read of done
within doSlow
is safe, because it is protected from concurrent writes by the mutex. Reading the value concurrently with atomic.LoadUint32
is safe because both are read operations.
这篇关于为什么在sync.Once中,atomic.StoreUint32比普通分配更可取?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!