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

Golang学习 - sync.Once —— 只执行一次的动作

sync.Once —— 只执行一次的动作

为什么需要 Once

在程序中有这样一类需求:某个初始化操作成本很高(比如加载配置文件、建立数据库连接、初始化缓存),我们希望在程序的生命周期内只执行一次,并且第一次需要它的时候才执行(惰性初始化)。

最朴素的想法是:

varconfig*ConfigfuncGetConfig()*Config{ifconfig==nil{config=loadConfig()}returnconfig}

这在单 goroutine 下没问题,但在并发场景有两大隐患:

  1. 多次初始化:多个 goroutine 同时看到config == nil,都去执行loadConfig()
  2. 部分初始化:即使把 nil 判断换成别的标记位,由于 CPU 缓存和编译器指令重排,一个 goroutine 可能看到标记位变了但数据还没写完——读到脏数据。

用 Mutex 可以解决第一个问题,但每次调用都加锁代价太高。用 RWMutex + double-check 可以部分缓解,但代码变得又长又容易写错。

sync.Once 就是为解决这个问题而生的。

Once 的用法

varonce sync.Oncevarinstance*SingletonfuncGetInstance()*Singleton{once.Do(func(){instance=&Singleton{data:make(map[string]string)}})returninstance}

Once 只有一个方法:

func(o*Once)Do(ffunc())
  • f没有参数也没有返回值。如果初始化需要返回值或错误,要通过闭包捕获外部变量。
  • 只有第一次调用Do才会真正执行f,后续调用直接跳过。
  • 无论多少个 goroutine 并发调用Dof只执行一次。
  • 所有在Do返回之后进入的 goroutine 都能看到f执行的全部副作用——Once 提供了 happens-before 保证。

内部原理

Once 的实现非常精简——就是一个 Mutex 加一个原子布尔值(Go 1.9+ 优化为先用 atomic 快路径判断,避免大部分场景的锁开销):

typeOncestruct{doneuint32m Mutex}func(o*Once)Do(ffunc()){ifatomic.LoadUint32(&o.done)==0{o.doSlow(f)}}

快路径:绝大多数调用通过原子读直接返回,无需锁。只有第一次调用(和极少数的竞争情况)才走慢路径加锁。

典型应用场景

1. 单例模式

Go 中最干净的单例写法:

var(instance*DB once sync.Once)funcGetDB()*DB{once.Do(func(){instance=&DB{conn:connect()}})returninstance}

对比其他语言的 double-check locking 或者静态初始化init()函数,Once 的写法:

  • 没有复杂的 if 嵌套
  • 没有显式的锁操作
  • 语义清晰直观

2. 延迟加载配置

程序启动时不加载,第一次请求配置时才加载:

var(config*AppConfig configOnce sync.Once)funcGetAppConfig()*AppConfig{configOnce.Do(func(){config=parseConfigFile("/etc/app/config.yaml")})returnconfig}

3. 资源关闭

确保某个清理动作只执行一次:

varcloseOnce sync.OncefuncShutdown(){closeOnce.Do(func(){db.Close()cache.Flush()logger.Sync()})}

即使多个 goroutine 因为各种原因调用了Shutdown,资源也只会被释放一次。

4. 初始化注册表

比如在包级别注册一些 handler,但注册操作依赖运行时信息:

var(handlersmap[string]Handler initOnce sync.Once)funcRegister(namestring,h Handler){initOnce.Do(func(){handlers=make(map[string]Handler)})handlers[name]=h}

关键注意事项

1. Once 不可复制

varo sync.Once o2:=o// 危险!复制后两个 Once 独立,各自能执行一次 Do

Aftero.Do()executes,o2.Do()will not know it’s done and will execute again — defeating the whole purpose.

2. Do 的参数函数不能 panic(或者说 panic 后不会复位)

如果Do(f)中的f发生了 panic,Once 会认为执行已完成。后续的Do不会再重试,相当于初始化"失败了但被记为完成"。

解决方案:在f内部 recover,或确保f不会 panic。

once.Do(func(){deferfunc(){ifr:=recover();r!=nil{// 此时 once 已被标记为 done,后续 Do 不会重试// 需要额外处理:也许把 once 重新赋值为零值,但这不是推荐做法}}()mustInit()})

更好的做法是让初始化函数返回 error,然后在外层判断:

var(instance*DB initErrerroronce sync.Once)funcGetDB()(*DB,error){once.Do(func(){instance,initErr=connectDB()})returninstance,initErr}

3. Once 不是用来限制函数调用次数的

它不是为了"最多调用一次"而设计的,而是为了"确保初始化发生一次"。如果你需要限制某个操作的总执行次数,应该用计数或其他机制。

Once 与 init() 的对比

特性sync.Onceinit()
执行时机第一次调用时(惰性)包被导入时(急切)
可控性程序主动触发语言自动触发
是否可跳过如从不调用则不执行导入即执行,无法跳过
适用场景延迟加载、按需初始化注册驱动、注入依赖

总结:优先用init()做一些轻量级的注册工作;重量级、不一定需要的初始化用sync.Once

小结

sync.Once 解决的是"有且仅有一次初始化"这个并发问题。它的核心价值在于用 3 行代码替换掉原本可能写成 20 行的 double-check locking,并且提供了内存可见性的保证。当你需要一个全局变量只被初始化一次,而且希望按需加载时,sync.Once 就是正确答案。

http://www.jsqmd.com/news/1204469/

相关文章:

  • VLA系统化落地:从π0到SmolVLA的全链路工程实践
  • 具身智能商业化困局:从0张大单到首单突破的七道关卡
  • 从0到1搭建数据指标体系:理论方法 + 技术落地全解析
  • ArkUI V2 状态管理深度拆解:告别 V1 深层刷新失效的全套解决方案
  • Windows专业卸载工具Revo Uninstaller Pro核心技术解析
  • MySQL8.X 双密码机制:解决应用程序用户不停机修改密码问题
  • 3DSident终极指南:深度解析任天堂3DS系统检测工具的技术架构与应用场景
  • VLA模型:具身智能的端到端动作理解范式
  • 2026年7月汽车擦车巾/汽车抛光打蜡毛巾行业热门厂家_宁波雅康家居用品有限公司 - 品牌宣传支持者
  • Windows 注册表实战:不改默认打开方式,只为 .bat 绑定 Zed 编辑器
  • C++ DLL封装实战:从原理到避坑的完整指南
  • Python单元测试Mock实战:pytest-mock五大高频场景解析
  • OpenClaw+飞书+千问智能办公体系稳定性实战指南
  • Jupyter Notebook与Python:AI算法入门最佳实践
  • iOS 14-16.6.1设备一键安装TrollStore:TrollInstallerX终极使用指南
  • kotlin数组操作
  • C++类与对象进阶:从构造析构到多态,掌握面向对象核心
  • vue 自定义模块内容
  • 泰格豪雅中国官方售后服务中心|服务电话及全部网点地址权威信息通告(2026年7月最新) - 亨得利官方服务中心
  • 候选路径生成:替代端到端轨迹预测的新范式
  • 扩散模型数学原理
  • 2026 年新发布:营口热门的双轴粉尘加湿机公司联系方式,别再买湿器了!双轴粉尘加湿机如何颠覆你的空气质量? - 企业官方推荐【认证】
  • Qwen3.5 27B大模型本地部署与开发实战指南
  • GraspVLA:视觉-语言-动作联合建模实现零样本灵巧抓取
  • AI技能全生命周期管理工具SkillNexus解析
  • 2026年7月网站建设/小程序定制开发专业团队推荐_亳州玖晟网络科技有限公司 - 行业平台推荐
  • BEAVR:基于VR的双手遥操作范式与实时具身映射架构
  • C++内存泄漏防治:从RAII到智能指针的五大实战策略
  • 如何通过yuzu模拟器实现Switch游戏的高性能跨平台运行
  • C++实现TOPSIS算法:高性能多准则决策分析实战指南