元素码农
基础
UML建模
数据结构
算法
设计模式
网络
TCP/IP协议
HTTPS安全机制
WebSocket实时通信
数据库
sqlite
postgresql
clickhouse
后端
rust
go
java
php
mysql
redis
mongodb
etcd
nats
zincsearch
前端
浏览器
javascript
typescript
vue3
react
游戏
unity
unreal
C++
C#
Lua
App
android
ios
flutter
react-native
安全
Web安全
测试
软件测试
自动化测试 - Playwright
人工智能
Python
langChain
langGraph
运维
linux
docker
工具
git
svn
🌞
🌙
目录
▶
Go运行时系统
▶
调度器原理
Goroutine调度机制
GMP模型详解
抢占式调度实现
系统线程管理
调度器源码实现分析
▶
网络轮询器
I/O多路复用实现
Epoll事件循环
异步IO处理
▶
系统监控
Sysmon监控线程
死锁检测机制
资源使用监控
▶
内存管理
▶
内存分配器
TCMalloc变体实现
mcache与mspan
对象分配流程
堆内存管理
▶
栈管理
分段栈实现
连续栈优化
栈扩容机制
▶
并发模型
▶
Channel实现
Channel底层结构
发送与接收流程
select实现原理
同步原语实现
▶
原子操作
CPU指令支持
内存顺序保证
sync/atomic实现
▶
并发原语
sync.Map实现原理
WaitGroup实现机制
Mutex锁实现
RWMutex读写锁
Once单次执行
Cond条件变量
信号量代码详解
信号量实现源码分析
信号量应用示例
▶
垃圾回收机制
▶
GC核心算法
三色标记法
三色标记法示例解析
写屏障技术
混合写屏障实现
▶
GC优化策略
GC触发条件
并发标记优化
内存压缩策略
▶
编译与链接
▶
编译器原理
AST构建过程
SSA生成优化
逃逸分析机制
▶
链接器实现
符号解析处理
重定位实现
ELF文件生成
▶
类型系统
▶
基础类型
类型系统概述
基本类型实现
复合类型结构
▶
切片与Map
切片实现原理
切片扩容机制
Map哈希实现
Map扩容机制详解
Map冲突解决
Map并发安全
▶
反射与接口
▶
类型系统
rtype底层结构
接口内存布局
方法表构建
▶
反射机制
ValueOf实现
反射调用代价
类型断言优化
▶
标准库实现
▶
同步原语
sync.Mutex实现
RWMutex原理
WaitGroup机制
▶
Context实现
上下文传播链
取消信号传递
Value存储优化
▶
time定时器实现
Timer实现原理
Ticker周期触发机制
时间轮算法详解
定时器性能优化
定时器源码分析
▶
执行流程
▶
错误异常
错误处理机制
panic与recover
错误传播最佳实践
错误包装与检查
自定义错误类型
▶
延迟执行
defer源码实现分析
▶
性能优化
▶
执行效率优化
栈内存优化
函数内联策略
边界检查消除
字符串优化
切片预分配
▶
内存优化
对象池实现
内存对齐优化
GC参数调优
内存泄漏分析
堆栈分配优化
▶
并发性能优化
Goroutine池化
并发模式优化
锁竞争优化
原子操作应用
Channel效率优化
▶
网络性能优化
网络轮询优化
连接池管理
网络缓冲优化
超时处理优化
网络协议调优
▶
编译优化
编译器优化选项
代码生成优化
链接优化技术
交叉编译优化
构建缓存优化
▶
性能分析工具
性能基准测试
CPU分析技术
内存分析方法
追踪工具应用
性能监控系统
▶
调试与工具
▶
dlv调试
dlv调试器使用
dlv命令详解
dlv远程调试
▶
调试支持
GDB扩展实现
核心转储分析
调试器接口
▶
分析工具
pprof实现原理
trace工具原理
竞态检测实现
▶
跨平台与兼容性
▶
系统抽象层
syscall封装
OS适配层
字节序处理
▶
cgo机制
CGO调用开销
指针传递机制
内存管理边界
▶
工程管理
▶
包管理
Go模块基础
模块初始化配置
依赖版本管理
go.mod文件详解
私有模块配置
代理服务设置
工作区管理
模块版本选择
依赖替换与撤回
模块缓存管理
第三方包版本形成机制
发布时间:
2025-04-24 23:09
↑
☰
# Once单次执行 ## 简介 sync.Once是Go语言标准库中提供的一种同步原语,用于确保某个操作在多个goroutine的环境中只会被执行一次。无论有多少个goroutine并发调用Once.Do方法,传递给Do的函数都只会被执行一次,而且所有的调用都会等待这次执行完成。 这种机制在初始化共享资源、创建单例对象、执行一次性设置等场景中非常有用。sync.Once的实现既简洁又高效,是Go语言并发工具箱中的重要组件。 ## 基本用法 ```go package main import ( "fmt" "sync" ) func main() { var once sync.Once done := make(chan bool) // 启动10个goroutine,每个都尝试执行初始化 for i := 0; i < 10; i++ { go func(id int) { fmt.Printf("Goroutine %d 尝试执行初始化\n", id) once.Do(func() { fmt.Printf("初始化操作由 Goroutine %d 执行\n", id) // 模拟耗时操作 for j := 0; j < 5; j++ { fmt.Printf("初始化中... %d/5\n", j+1) } fmt.Println("初始化完成") }) fmt.Printf("Goroutine %d 继续执行\n", id) done <- true }(i) } // 等待所有goroutine完成 for i := 0; i < 10; i++ { <-done } } ``` 输出结果将显示初始化操作只被执行了一次,而所有的goroutine都会等待这次执行完成后才继续执行。 ## 内部结构 sync.Once的内部结构非常简单: ```go type Once struct { done uint32 m Mutex } ``` - `done`:一个标志位,用于标记操作是否已经执行过,使用uint32类型以支持原子操作 - `m`:一个互斥锁,用于保护初始化过程的并发安全 ## 实现原理 sync.Once的核心是Do方法,其实现如下: ```go func (o *Once) Do(f func()) { // 快速路径:检查操作是否已完成 if atomic.LoadUint32(&o.done) == 1 { return } // 慢速路径:需要执行初始化 o.m.Lock() defer o.m.Unlock() // 双重检查,避免在获取锁的过程中其他goroutine已经完成了初始化 if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } } ``` 实现原理可以概括为以下几点: 1. **快速路径**:首先通过原子操作检查操作是否已经执行过,如果已执行则直接返回 2. **互斥锁保护**:如果操作未执行,则获取互斥锁,确保只有一个goroutine能够执行初始化 3. **双重检查**:在获取锁之后,再次检查操作是否已执行,避免在等待锁的过程中其他goroutine已经完成了初始化 4. **执行并标记**:执行初始化函数,并通过原子操作将done标志设为1,表示操作已完成 这种实现方式结合了原子操作和互斥锁的优点,既保证了并发安全,又尽可能地减少了锁的竞争。 ## 使用场景 ### 单例模式 sync.Once最常见的用途是实现单例模式,确保全局只有一个实例: ```go type Singleton struct { // 单例的字段 } var ( instance *Singleton once sync.Once ) func GetInstance() *Singleton { once.Do(func() { instance = &Singleton{} // 初始化实例 }) return instance } ``` 这种实现比使用互斥锁的双重检查锁定(Double-Checked Locking)更简洁、更安全。 ### 延迟初始化 当某些资源的初始化成本较高,但又不一定会被使用时,可以使用sync.Once实现延迟初始化: ```go type ExpensiveResource struct { // 资源字段 } type ResourceManager struct { resource *ExpensiveResource once sync.Once } func (rm *ResourceManager) GetResource() *ExpensiveResource { rm.once.Do(func() { fmt.Println("初始化昂贵资源...") rm.resource = &ExpensiveResource{} // 初始化资源 }) return rm.resource } ``` ### 一次性设置 在程序启动时需要执行一些全局设置,但又希望确保这些设置只执行一次: ```go var ( globalConfig *Config setupOnce sync.Once ) func SetupGlobalConfig() { setupOnce.Do(func() { fmt.Println("加载全局配置...") globalConfig = loadConfigFromFile() initLogging() connectToDatabase() }) } ``` ### 并发安全的初始化 当多个goroutine可能同时访问某个需要初始化的资源时: ```go type Worker struct { conn *DatabaseConnection initOnce sync.Once } func (w *Worker) Process(data []byte) error { w.initOnce.Do(func() { w.conn = connectToDatabase() }) return w.conn.Execute(data) } ``` ## 使用注意事项 1. **不可重置**:sync.Once的状态一旦设置为已完成,就不能重置。如果需要多次执行初始化,应该创建新的Once实例 2. **panic处理**:如果Do方法中的函数发生panic,sync.Once会认为操作已完成,后续调用不会再执行该函数 ```go var once sync.Once // 第一次调用会panic once.Do(func() { panic("初始化失败") }) // 第二次调用不会执行,即使第一次失败了 once.Do(func() { fmt.Println("这不会被执行") }) ``` 3. **不同函数的执行**:sync.Once只保证同一个Once实例的Do方法只执行一次,而不关心传入的是什么函数 ```go var once sync.Once // 第一次调用会执行 once.Do(func() { fmt.Println("函数A") }) // 第二次调用不会执行,即使是不同的函数 once.Do(func() { fmt.Println("函数B") }) ``` 4. **避免死锁**:不要在Do方法中再次调用同一个Once实例的Do方法,这会导致死锁 ```go var once sync.Once once.Do(func() { // 这会导致死锁 once.Do(func() { fmt.Println("嵌套调用") }) }) ``` ## 性能考虑 sync.Once的实现在性能上做了很好的优化: 1. **快速路径**:对于已初始化的情况,只需要一个原子加载操作,几乎没有性能开销 2. **锁竞争**:只有在第一次执行时才需要获取互斥锁,后续调用不会有锁竞争 3. **内存开销**:sync.Once只包含一个uint32和一个Mutex,内存开销很小 在大多数情况下,使用sync.Once的性能开销可以忽略不计,特别是与它所解决的并发安全问题相比。 ## 与其他并发控制机制的比较 | 机制 | 用途 | 优点 | 缺点 | |-----|-----|------|------| | sync.Once | 确保操作只执行一次 | 简洁、高效、专为一次性执行设计 | 不可重置,不支持条件执行 | | Mutex | 互斥访问共享资源 | 灵活,可以保护任意临界区 | 需要手动实现单次执行逻辑,容易出错 | | atomic | 原子操作 | 性能最好,无锁 | 功能有限,需要额外逻辑实现单次执行 | | Channel | 通信和同步 | 可以实现复杂的同步模式 | 实现单次执行较为复杂 | ## 实现自定义的可重置Once 标准库的sync.Once不支持重置,如果需要这个功能,可以实现一个自定义的可重置Once: ```go type ResettableOnce struct { m sync.Mutex done bool } func (o *ResettableOnce) Do(f func()) { o.m.Lock() defer o.m.Unlock() if !o.done { f() o.done = true } } func (o *ResettableOnce) Reset() { o.m.Lock() defer o.m.Unlock() o.done = false } ``` 这个实现不如标准库的sync.Once高效,因为它没有使用原子操作的快速路径,但它提供了重置功能。 ## 总结 sync.Once是Go语言中一个简单而强大的同步原语,它解决了在并发环境中确保某个操作只执行一次的问题。通过巧妙地结合原子操作和互斥锁,sync.Once既保证了并发安全,又提供了良好的性能。 在实际开发中,sync.Once特别适用于单例模式、延迟初始化、一次性设置等场景。理解sync.Once的实现原理和使用注意事项,有助于我们更好地利用这个工具,编写高效、安全的并发程序。