Backend Development 7 min read

Mastering sync.Once in Go: Thread‑Safe Singleton Patterns Explained

This article explains Go's sync.Once primitive, compares it with init, details various singleton implementations—including lazy, eager, and double‑checked locking—and provides practical code examples and a deep dive into its source implementation for thread‑safe one‑time initialization.

360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
Mastering sync.Once in Go: Thread‑Safe Singleton Patterns Explained

sync.Once Introduction

sync.Once is a Go standard‑library primitive that guarantees a function runs only once, making it ideal for singleton patterns such as initializing configuration or maintaining a database connection. Unlike the init function, sync.Once can be invoked lazily at any point in the code and is safe for concurrent use.

Usage Scenarios

Understanding common singleton implementations helps illustrate when sync.Once is appropriate. The classic lazy ("懒汉") pattern creates the instance on first use, but it is not thread‑safe:

<code>type DemoModel struct{}
var ins *DemoModel
func InsDemoModel() *DemoModel {
    if ins == nil {
        ins = new(DemoModel)
    }
    return ins
}</code>

The eager ("饿汉") pattern instantiates the object at program start, which can increase startup time and waste memory if the instance is never used:

<code>type DemoModel struct{}
var ins *DemoModel = new(DemoModel)
func InsDemoModel() *DemoModel { return ins }</code>

Adding a mutex makes the lazy version thread‑safe but incurs locking overhead on every call:

<code>type DemoModel struct{}
var ins *DemoModel
var mu sync.Mutex
func InsDemoModel() *DemoModel {
    mu.Lock()
    defer mu.Unlock()
    if ins == nil {
        ins = new(DemoModel)
    }
    return ins
}</code>

Double‑checked locking reduces the locking cost by first checking the instance without a lock, then acquiring the lock only when necessary:

<code>type DemoModel struct{}
var ins *DemoModel
var mu sync.Mutex
func InsDemoModel() *DemoModel {
    if ins == nil {
        mu.Lock()
        defer mu.Unlock()
        if ins == nil {
            ins = new(DemoModel)
        }
    }
    return ins
}</code>

sync.Once Usage Example

Combining sync.Once with a singleton eliminates both race conditions and unnecessary locking:

<code>type DemoModel struct{}
var (
    once sync.Once
    ins  *DemoModel
)
func InsDemoModel() *DemoModel {
    once.Do(func() {
        ins = new(DemoModel)
        log.Println("Get DemoModel instance")
    })
    return ins
}
func main() {
    for i := 0; i < 10; i++ {
        go func() { _ = InsDemoModel() }()
    }
    time.Sleep(time.Second)
}</code>

Running the program prints the log line only once, confirming that the initialization function executed a single time:

<code>$ go run main.go
2021/06/23 20:27:06 Get DemoModel instance</code>

sync.Once Implementation Details

The core of sync.Once is a struct with a done flag and a mutex. Placing done as the first field reduces instruction count on the hot path, improving performance on many architectures.

<code>package sync

import "sync/atomic"

type Once struct {
    done uint32
    m    Mutex
}

func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 0 {
        o.doSlow(f)
    }
}

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>

Comments in the source explain that the done field is placed first to keep the hot‑path code compact, allowing the compiler to generate fewer instructions when checking whether the action has already been performed.

concurrencyGoThread SafetySingletonsync.OnceLazy Initialization
360 Zhihui Cloud Developer
Written by

360 Zhihui Cloud Developer

360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.