sync.Once
is a type in the Go standard library’s sync
package, designed to ensure that a specific operation is executed only once in a concurrent environment. It is commonly used for initializing resources or performing one-time setup operations, avoiding issues that arise from repeated execution.
Core Functionality
The core method of sync.Once
is Do
, which has the following signature:
1func (o *Once) Do(f func())
The Do
method takes a function f
as an argument and guarantees that this function will be executed only once, even if Do
is called multiple times.
Use Cases
- Singleton Pattern: Ensuring the creation of a globally unique instance.
- Initialization Operations: Such as database connections or configuration loading, ensuring they are performed only once.
- Resource Release: Ensuring resources are released only once, avoiding duplicate releases.
Internal Implementation
sync.Once
internally uses a uint32
flag and a mutex m
to achieve its functionality. The Do
method first checks the flag. If it is 0, it executes the provided function and sets the flag to 1, ensuring the function is executed only once.
1package sync
2
3import (
4 "sync/atomic"
5)
6
7type Once struct {
8 done atomic.Uint32
9 m Mutex
10}
11
12func (o *Once) Do(f func()) {
13 if o.done.Load() == 0 {
14 o.doSlow(f)
15 }
16}
17
18func (o *Once) doSlow(f func()) {
19 o.m.Lock()
20 defer o.m.Unlock()
21 if o.done.Load() == 0 {
22 defer o.done.Store(1)
23 f()
24 }
25}
Considerations
sync.Once
is not reusable. Once the Do
method executes the provided function, subsequent calls to Do
will not execute the function again. However, it can be adjusted to allow the function to be executed again if it returns an error.
1package zonce
2
3import (
4 "sync"
5 "sync/atomic"
6)
7
8type Once struct {
9 done uint32
10 sync.Mutex
11}
12
13// The provided function f returns an error. If initialization fails, it returns the error.
14// The Do method returns this error to the caller.
15func (o *Once) Do(f func() error) error {
16 if atomic.LoadUint32(&o.done) == 1 { //fast path
17 return nil
18 }
19 return o.doSlow(f)
20}
21
22func (o *Once) doSlow(f func() error) error {
23 o.Lock()
24 defer o.Unlock()
25 var err error
26 if o.done == 0 {
27 if err := f(); err == nil {
28 // Set the flag to initialized only if initialization is successful
29 atomic.StoreUint32(&o.done, 1)
30 }
31 }
32 return err
33}