当前位置:首页 > Go > 正文

Go语言中的 sync.Once(实现并发安全的利器)

在 Go 语言开发中,我们经常会遇到需要确保某段代码在整个程序生命周期内只执行一次的场景。比如初始化配置、连接数据库、加载缓存等。这时候,sync.Once 就派上用场了。

sync.Once 是 Go 标准库 sync 包提供的一个结构体,用于保证某个操作在并发环境下只执行一次,并且是线程安全(即并发安全)的。

Go语言中的 sync.Once(实现并发安全的利器) Go语言 sync.Once 并发安全 单例模式 第1张

为什么需要 sync.Once?

假设你有一个函数,它负责初始化全局资源(例如数据库连接)。在多 goroutine 环境下,如果不加控制,多个 goroutine 可能同时调用这个初始化函数,导致资源被重复创建、配置冲突,甚至程序崩溃。

虽然你可以使用互斥锁(sync.Mutex)来保护这段代码,但那样写起来繁琐,而且容易出错。而 sync.Once 提供了一种更简洁、更安全的方式。

sync.Once 的基本用法

sync.Once 只有一个公开方法:Do(f func())。无论你调用多少次 Do,传入的函数 f 都只会被执行一次。

下面是一个简单的例子:

package mainimport (	"fmt"	"sync")var once sync.Oncefunc initConfig() {	fmt.Println("正在初始化配置...")}func main() {	var wg sync.WaitGroup	for i := 0; i < 5; i++ {		wg.Add(1)		go func(id int) {			defer wg.Done()			// 多个 goroutine 同时调用 once.Do			once.Do(initConfig)			fmt.Printf("Goroutine %d 完成\n", id)		}(i)	}	wg.Wait()}

运行结果(顺序可能不同):

正在初始化配置...Goroutine 4 完成Goroutine 0 完成Goroutine 1 完成Goroutine 2 完成Goroutine 3 完成  

可以看到,尽管有 5 个 goroutine 同时调用了 once.Do(initConfig),但 initConfig 函数只打印了一次 —— 这就是 sync.Once 的魔力!

实战:用 sync.Once 实现并发安全的单例模式

在 Go 中,单例模式(Singleton Pattern)常用于确保某个结构体在整个程序中只有一个实例。结合 sync.Once,我们可以轻松写出线程安全的单例。

package mainimport (	"fmt"	"sync")// Database 模拟一个数据库连接type Database struct {	conn string}var (	instance *Database	once     sync.Once)// GetInstance 返回唯一的 Database 实例func GetInstance() *Database {	once.Do(func() {		fmt.Println("创建数据库连接...")		instance = &Database{conn: "mysql://user:pass@localhost/db"}	})	return instance}func main() {	var wg sync.WaitGroup	// 启动多个 goroutine 获取实例	for i := 0; i < 3; i++ {		wg.Add(1)		go func(id int) {			defer wg.Done()			db := GetInstance()			fmt.Printf("Goroutine %d 获取到实例: %p\n", id, db)		}(i)	}	wg.Wait()}

输出示例:

创建数据库连接...Goroutine 2 获取到实例: 0xc0000a4018Goroutine 0 获取到实例: 0xc0000a4018Goroutine 1 获取到实例: 0xc0000a4018  

所有 goroutine 获取到的是同一个内存地址,说明确实是同一个实例,并且初始化只发生了一次。这就是 并发安全 的体现。

注意事项

  • sync.Once 不是可重置的。一旦 Do 执行完成,就永远不会再执行其他函数。
  • 不要试图“复用”一个已经执行过的 Once 对象。每个“只执行一次”的逻辑应使用独立的 Once 实例。
  • Do 中的函数如果 panic,Once 会认为这次执行已完成,后续调用将不再执行该函数(也不会再次 panic)。

总结

sync.Once 是 Go 语言中实现“只执行一次”语义的标准工具,它简单、高效、并发安全。无论是初始化全局资源,还是实现单例模式,它都是最佳选择之一。

掌握 Go语言 中的 sync.Once,能让你写出更健壮、更高效的并发程序。记住:在需要确保某段代码仅运行一次的场景下,优先考虑使用 sync.Once,而不是自己手写锁逻辑。

希望这篇教程能帮助你理解 并发安全单例模式 在 Go 中的优雅实现!