Proper Usage of Atomic Operations in Go's sync.Once
In the context of Go's sync.Once implementation, it is crucial to understand the distinction between normal assignment and the atomic.StoreUint32 operation when setting the done flag.
The Incorrect Implementation
Initially, the Do function in once.go utilized the following approach:
if atomic.CompareAndSwapUint32(&o.done, 0, 1) { f() }
This implementation fails to guarantee that the execution of f is complete upon the return of Do. Two concurrent calls to Do could result in the first call successfully calling f, while the second call returns prematurely, believing f has finished, even though it hasn't.
Atomic Store Operation
To address this issue, Go employs the atomic.StoreUint32 operation. Unlike normal assignment, atomic.StoreUint32 ensures the visibility of the updated done flag to other goroutines.
Memory Model Considerations
The use of atomic operations in sync.Once is not primarily influenced by the memory model of the underlying machine. Go's memory model acts as a unifying abstraction, ensuring consistent behavior across different hardware platforms, regardless of their specific memory models.
Optimized Fast Path
To optimize performance, sync.Once employs a fast path for common scenarios where the done flag is already set. This fast path makes use of atomic.LoadUint32 to check the done flag without acquiring the mutex. If the flag is set, the function returns immediately.
Slow Path with Mutex and Atomic Store
When the fast path fails (i.e., done is initially unset), the slow path is entered. A mutex is acquired to ensure that only one caller can proceed to execute f. After f is completed, atomic.StoreUint32 is used to set the done flag, making it visible to other goroutines.
Concurrent Reads
Even though the done flag is set atomically, it does not make concurrent reads safe. Reading the flag outside of the protected critical section requires the use of atomic.LoadUint32. However, direct reads within the critical section are safe due to the mutex providing mutual exclusion.
In summary, Go's sync.Once utilizes atomic.StoreUint32 to ensure the consistent and visible modification of the done flag, regardless of the underlying memory モデル and to avoid data races. The combination of atomic operations and mutexes provides both performance optimizations and correctness guarantees.
The above is the detailed content of Why does Go\'s `sync.Once` use `atomic.StoreUint32` instead of normal assignment to set the `done` flag?. For more information, please follow other related articles on the PHP Chinese website!