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}