Home  >  Article  >  Backend Development  >  Why does `sync.Once` use `atomic.StoreUint32` instead of a regular assignment for the `done` flag?

Why does `sync.Once` use `atomic.StoreUint32` instead of a regular assignment for the `done` flag?

Barbara Streisand
Barbara StreisandOriginal
2024-10-31 05:40:01679browse

Why does `sync.Once` use `atomic.StoreUint32` instead of a regular assignment for the `done` flag?

AtomicStoreUint32 vs. Assignment in Sync.Once

While exploring the source code for Go's sync.Once type, a question arises regarding the usage of atomic.StoreUint32 versus a regular assignment for setting the done flag.

Incorrect Implementation:

The original source code contained an incorrect implementation:

<code class="go">func (o *Once) Do(f func()) {
    if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
        f()
    }
}</code>

This implementation fails to guarantee that f is complete when the function returns. Simultaneous calls could lead to the winner executing f while the second caller returns immediately, assuming the first call is complete, which may not be the case.

Correct Implementation:

To rectify this issue, the current implementation employs atomic.StoreUint32 in conjunction with a mutex:

<code class="go">func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 0 {
        o.doSlow(f)
    }
}</code>
<code class="go">func (o *Once) doSlow(f func()) {
    o.m.Lock()
    defer o.m.Unlock()
    if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}</code>

Why AtomicStoreUint32?

The use of atomic.StoreUint32 is necessary to ensure that other goroutines can observe the change to o.done after f completes. While primitive assignments may be atomic on certain architectures, Go's memory model requires the use of the atomic package to guarantee atomic operations across all supported architectures.

Access to Done Flag:

The goal is to ensure that access to the done flag is safe outside of the mutex. Therefore, atomic operations are utilized instead of locking with a mutex. This optimization enhances the efficiency of the fast path, enabling sync.Once to be deployed in high-traffic scenarios.

Mutex for doSlow:

The mutex within doSlow ensures that only one caller executes f before o.done is set. atomic.StoreUint32 is used to write the flag because it may occur concurrently with atomic.LoadUint32 outside the mutex's critical section.

Concurrent Writes vs. Reads:

Directly reading o.done in doSlow is safe due to the mutex protection. Additionally, reading o.done concurrently with atomic.LoadUint32 is safe because both operations involve reading only.

The above is the detailed content of Why does `sync.Once` use `atomic.StoreUint32` instead of a regular assignment for 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