返回

踩坑!初级程序猿须知:Sync.Once的神奇用法

后端

掌握 sync.Once,轻松驾驭并发编程

前言

在 Go 语言的并发编程中,协调并发任务至关重要。sync.Once 是一个简单但强大的工具,可帮助您确保特定操作只执行一次,即使有多个 goroutine 同时尝试执行。了解并掌握 sync.Once 的用法,将大幅提升您编写健壮、可靠并发程序的能力。

初识 sync.Once

sync.Once 是一种并发类型,它只有一个方法:Do。此方法接收一个函数作为参数,并保证该函数只会被执行一次。也就是说,无论调用 sync.Once.Do 多次,传入的函数都只会执行一次。

使用场景

sync.Once 在以下场景中特别有用:

  • 单例模式: 确保只有一个实例被创建。
  • 资源初始化: 保证资源只被初始化一次。
  • 并发控制: 确保某段代码只被执行一次。

源码剖析

sync.Once 的实现非常简单,它使用了一个互斥锁和一个原子变量:

type Once struct {
    m    sync.Mutex
    done uint32
}

func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 0 {
        o.m.Lock()
        defer o.m.Unlock()
        if atomic.LoadUint32(&o.done) == 0 {
            f()
            atomic.StoreUint32(&o.done, 1)
        }
    }
}

当调用 sync.Once.Do 时,它首先会检查原子变量是否为 0。如果不是,说明函数已经执行过,它将直接返回,而不会执行函数 f。

如果原子变量为 0,说明函数还没有被执行过,它将获取互斥锁,然后再次检查原子变量的值。如果仍然为 0,则执行函数 f,并将原子变量的值设置为 1,以表示函数已经执行过。

实战应用

以下是 sync.Once 在不同场景中的实际应用:

  • 单例模式:
var instance *MyStruct

var once sync.Once

func GetInstance() *MyStruct {
    once.Do(func() {
        instance = new(MyStruct)
    })
    return instance
}
  • 资源初始化:
var db *sql.DB

var once sync.Once

func GetDB() *sql.DB {
    once.Do(func() {
        db = sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database")
    })
    return db
}
  • 并发控制:
var once sync.Once

func MyFunc() {
    once.Do(func() {
        // do something
    })
}

结语

sync.Once 是一个看似简单,但功能强大的并发控制工具。它可以轻松实现单例模式、资源初始化和并发控制等场景。掌握 sync.Once 的使用,将让您在并发编程中游刃有余。

常见问题解答

  1. sync.Once 和 sync.Mutex 有什么区别?
    sync.Once 保证函数只执行一次,而 sync.Mutex 保证一段代码被互斥执行。
  2. 如何检查 sync.Once 是否已经执行过?
    可以使用 atomic.LoadUint32(&o.done) == 1 来检查。
  3. sync.Once 可以用于哪些并发场景?
    单例模式、资源初始化、并发控制。
  4. sync.Once 的使用是否会影响性能?
    正常情况下,sync.Once 的使用不会显著影响性能。
  5. sync.Once 是否可以用于跨 goroutine 的同步?
    可以,但需要将 sync.Once 声明为全局变量。