Home  >  Article  >  Backend Development  >  Why does Go\'s `sync.Once` use `atomic.StoreUint32` instead of normal assignment to set the `done` flag?

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

Patricia Arquette
Patricia ArquetteOriginal
2024-10-31 10:48:02850browse

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

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!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn