目录

go-sync.Once-源码分析

go sync.Once 源码分析

sync.Once 是 Go 语言标准库中的一个同步原语,用于确保某个操作或函数在并发环境下只执行一次。它通常用于以下场景:

  1. 单例模式 :确保全局只有一个实例对象,避免重复创建资源
  2. 延迟初始化 :在程序运行过程中,当真正需要某个资源时才进行初始化
  3. 执行性一次的操作 :例如加载配置文件、初始化日志系统等

使用方法

sync.Once 提供了一个方法 Do ,接受一个函数作为参数。无论 Do 被调用多少次,传入的函数只会执行一次。例如:

var once sync.Once
var config *Config

func ReadConfig() *Config {
    once.Do(func() {
        config = &Config{Server: "example.com", Port: 8080}
    })
    return config
}

在这个例子中, config 的初始化只会在第一次调用 ReadConfig 时执行,后续调用直接返回已初始化的 config

特点

  • 线程安全 :即使在多个协程同时调用 Do 方法,也能保证函数只执行一次。
  • 不可重用 :一旦 sync.Once 执行完成,它将标记为“已完成”,后续调用不会重新执行。
  • 异常处理 :如果 Do 中的函数发生 panic, sync.Once 会将其视为“已完成”,后续调用不会重新执行。

源码分析

sync.Once 的实现基于 双重锁检查机制 实现的。它通过 atomic.LoadUint32atomic.StoreUint32 来检查和标记操作是否已完成。这种设计既保证了性能,又确保了线程安全。

package sync

import (
	"sync/atomic"
)

type Once struct {
	done uint32
	m    Mutex
}

func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		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() 触发 panic 也会标记为 “已完成”,后续调用不会重新执行
		f()
	}
}

双重锁检查机制

双重锁检查机制(Double-Checked Locking,DCL)是一种用于多线程环境中实现延迟初始化单例模式的设计模式,其核心思想是通过两次检查来减少同步锁的开销,同时保证线程安全。

工作原理

  1. 第一次检查 :在进入同步代码块之前,先检查实例是否已经被初始化。如果实例已经存在,则直接返回实例,避免进入同步块。
  2. 加锁 :如果实例尚未初始化,线程将尝试获取锁,以确保只有一个线程可以进入初始化代码块。
  3. 第二次检查 :在获取锁后,再次检查实例是否已经被初始化。这是为了确保在当前线程等待锁的过程中,其他线程没有初始化实例。