Skip to content

Once

Go sync.Once 详解

sync.Once 是 Go 提供的一种同步原语,它确保某个操作只执行一次,无论该操作被调用多少次。这对于初始化操作或其他只需要执行一次的操作非常有用。sync.Once 通常用于延迟初始化(lazy initialization)或确保单例模式的实现。

1. 基本概念

  • sync.Once 类型的实例提供了一个方法 Do(f func()),它保证给定的函数 f 只会执行一次。
  • 即使 Do() 被调用多次,传入的函数 f 只会在第一次调用时执行一次,后续调用不会再次执行该函数。

2. sync.Once 的方法

  • Do(f func()):执行给定的函数 f,并确保该函数只执行一次。

3. sync.Once 的使用示例

以下是一个使用 sync.Once 确保某个初始化操作只执行一次的示例:

go
package main

import (
    "fmt"
    "sync"
)

var (
    once sync.Once
    initialized bool
)

func initialize() {
    fmt.Println("Initializing...")
    initialized = true
}

func main() {
    var wg sync.WaitGroup

    // 启动多个 Goroutine,尝试执行初始化操作
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d\n", id)
            once.Do(initialize)  // 只会执行一次
        }(i)
    }

    wg.Wait()
    fmt.Println("Initialized:", initialized)
}

输出

Goroutine 0
Goroutine 1
Goroutine 2
Goroutine 3
Goroutine 4
Initializing...
Initialized: true

在这个示例中:

  • once.Do(initialize) 确保 initialize() 只会被调用一次。尽管启动了多个 Goroutine,它们都调用了 Do() 方法,但初始化函数只会执行一次。
  • once 对象确保了并发调用时的线程安全,且不会重复执行 initialize 函数。

4. sync.Once 的工作原理

sync.Once 内部使用了原子操作来保证函数的只执行一次。当 Do() 被第一次调用时,它会执行传入的函数,并将一个标志设置为已执行。之后再次调用 Do() 时,函数不会再被执行,即使它被多个 Goroutine 调用。

具体来说,Do() 的执行过程如下:

  1. 第一次调用时,执行传入的函数,并将内部标记设置为 "已执行"。
  2. 后续对 Do() 的调用会检查这个标记,如果已经执行过,就直接返回,不再执行函数。

5. sync.Once 与单例模式

sync.Once 是实现单例模式的理想工具之一。它可以确保一个实例只创建一次,适用于应用中只需要一个全局共享实例的情况。

go
package main

import (
    "fmt"
    "sync"
)

var (
    once   sync.Once
    singleInstance *MySingleton
)

type MySingleton struct {
    Name string
}

func GetSingletonInstance() *MySingleton {
    once.Do(func() {
        singleInstance = &MySingleton{Name: "Singleton Instance"}
    })
    return singleInstance
}

func main() {
    instance1 := GetSingletonInstance()
    instance2 := GetSingletonInstance()

    fmt.Println(instance1.Name)
    fmt.Println(instance2.Name)

    // 验证两者是否是同一个实例
    fmt.Println(instance1 == instance2)  // 输出: true
}

输出

Singleton Instance
Singleton Instance
true

在这个示例中:

  • GetSingletonInstance() 使用 sync.Once 来保证 MySingleton 的实例只会创建一次。
  • 即使多次调用 GetSingletonInstance(),返回的都是同一个实例。

6. sync.Once 的应用场景

  • 延迟初始化:在多线程环境下,确保某个初始化操作只执行一次,避免重复执行昂贵的初始化操作。
  • 单例模式:保证在应用中只存在一个实例,避免并发操作中的多次创建。
  • 资源加载:例如,配置文件、数据库连接等资源的加载,可以使用 Once 来保证只加载一次。

7. 性能考虑

  • sync.Once 是线程安全的,并且能够在并发环境下高效地确保某个操作只执行一次。
  • 它适用于确保初始化或资源分配操作的惰性执行,特别是对于高并发的场景,它能避免不必要的锁定和重复计算。

8. 总结

  • sync.Once 提供了一种确保某个操作只执行一次的机制,非常适合用在延迟初始化、单例模式和资源加载等场景中。
  • 使用 Do(f func()) 方法,第一次调用时执行传入的函数,后续的调用不会再执行该函数。
  • sync.Once 是并发安全的,它能够避免函数多次执行,同时对性能影响较小,适合在高并发环境中使用。