0

0

如何在Golang中实现单次执行Sync.Once Go语言单例模式线程安全

P粉602998670

P粉602998670

发布时间:2026-03-18 15:42:31

|

799人浏览过

|

来源于php中文网

原创

Sync.Once.Do只执行一次,因其用atomic操作控制uint32类型的done字段,函数返回(含panic)后done置1,后续调用立即返回;panic不重试,需新建Once实例。

如何在golang中实现单次执行sync.once go语言单例模式线程安全

Sync.Once.Do 为什么只执行一次

Sync.Once 的核心是内部一个 uint32 类型的 done 字段,用原子操作 atomic.LoadUint32atomic.CompareAndSwapUint32 控制状态。它不依赖锁阻塞 goroutine,而是让后续调用者「自旋等待」直到函数返回——但注意:这个等待不是忙等,Once 内部用了 runtime_Semacquire 等待信号量,实际是挂起 goroutine。

  • 只要 Do 传入的函数返回(无论 panic 还是正常结束),done 就被置为 1,后续所有调用立刻返回,不再执行函数体
  • 如果函数 panic,Once 仍认为“已执行”,不会重试——这点常被忽略,错误处理必须在函数内部做
  • 不能靠多次调用 Do 触发重试,要重试得新建一个 sync.Once 实例

单例初始化时怎么安全返回值

sync.Once 本身不返回值,所以常见写法是配合包级变量 + 惰性初始化函数。关键在于:初始化逻辑和返回值获取必须在同一作用域内完成,避免竞态。

  • 不要这样写:var instance *MyType; func GetInstance() *MyType { once.Do(func(){ instance = new(MyType) }); return instance } —— instance 赋值和读取之间没有 happens-before 保证(虽然实际中因 once 的内存屏障通常安全,但语义不明确)
  • 推荐写法:把实例声明、初始化、返回全包进 Do 的闭包里,用闭包变量承接结果
  • 示例:
    var (
        instance *DB
        once     sync.Once
    )
    
    func GetDB() *DB {
        once.Do(func() {
            instance = &DB{conn: connectToDB()}
        })
        return instance
    }

Once.Do 传函数时容易踩的坑

最典型问题是「提前求值」:把带参数的函数直接调用后传进去,导致每次 Do 都执行一次,失去单次语义。

  • 错误:once.Do(connectDB()) —— connectDB() 立即执行,返回值(可能是 nil 或函数)传给 Do,根本没用
  • 错误:once.Do(func() { initConfig(cfgPath) }),但 cfgPath 是外部变量且可能被修改 —— 闭包捕获的是变量引用,不是快照
  • 正确:确保传入的是函数字面量,且所有外部依赖在闭包创建时已确定;必要时显式拷贝值:path := cfgPath; once.Do(func() { initConfig(path) })
  • 另一个坑:Do 接收 func(),不能传带返回值的函数,也不能传方法值(除非接收者是地址且类型匹配)

和 double-checked locking 对比有啥实际区别

Go 里有人手写双重检查(先读 volatile 变量,再加锁再判),但没必要。Go 的 sync.Once 在底层做了更精细的优化:它用 unsafe.Pointer + 原子操作 + 协程调度协作,避免了锁竞争开销,也规避了 C/C++ 里因内存模型宽松导致的重排序问题。

Riffo
Riffo

Riffo是一个免费的文件智能命名和管理工具

下载

立即学习go语言免费学习笔记(深入)”;

  • 性能上:首次调用略慢(需初始化信号量),后续调用几乎零开销(一次原子读);而手写双重检查每次都要至少一次原子读 + 条件判断
  • 可读性和维护性差太多:手写容易漏掉 runtime.Gosched 或内存屏障,Once 是标准库验证过的模式
  • 兼容性无差别:所有 Go 版本都支持,无需考虑 go version < 1.9atomic.Value 替代方案

真正复杂的地方在于:一旦初始化失败(比如配置加载出错、网络不可达),你没法通过 Once 机制自动恢复——它就卡死了。这种场景得自己加兜底逻辑,比如 fallback 实例、重试计数器,或者换用更灵活的 lazy-init 包。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

510

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

204

2025.07.04

go语言闭包相关教程大全
go语言闭包相关教程大全

本专题整合了go语言闭包相关数据,阅读专题下面的文章了解更多相关内容。

153

2025.07.29

Python WebSocket实时通信与异步服务开发实践
Python WebSocket实时通信与异步服务开发实践

本专题聚焦 Python 在实时通信场景中的开发实践,系统讲解 WebSocket 协议原理、长连接管理、消息推送机制以及异步服务架构设计。内容包括客户端与服务端通信实现、连接稳定性优化、消息队列集成及高并发处理策略。通过完整案例,帮助开发者构建高效稳定的实时通信系统,适用于聊天应用、实时数据推送等场景。

5

2026.03.18

Java Spring Security权限控制与认证机制实战
Java Spring Security权限控制与认证机制实战

本专题围绕 Java 后端安全体系建设展开,重点讲解 Spring Security 在权限控制与认证机制中的应用实践。内容涵盖用户认证流程、权限模型设计、JWT 鉴权方案、OAuth2 集成以及接口安全防护策略。通过实际项目案例,帮助开发者构建安全可靠的后端认证体系,提升系统安全性与可扩展能力。

21

2026.03.18

抖漫入口地址合集
抖漫入口地址合集

本专题整合了抖漫入口地址相关合集,阅读专题下面的文章了解更多详细地址。

137

2026.03.17

多环境下的 Nginx 安装、结构与运维实战
多环境下的 Nginx 安装、结构与运维实战

本专题聚焦多环境下Nginx实战,详解开发、测试及生产环境的差异化安装策略与目录结构规划。深入剖析配置模块化设计、灰度发布流程及跨环境同步机制。结合监控告警、故障排查与自动化运维工具,提供全链路管理方案,助力团队构建灵活、高可用的Nginx服务体系,从容应对复杂业务场景挑战。

14

2026.03.17

PS 批量添加图片
PS 批量添加图片

本专题整合了PS批量添加图片教程合集,阅读专题下面的文章了解更多详细操作。

14

2026.03.17

Nginx 基础架构:从安装配置到系统化管理
Nginx 基础架构:从安装配置到系统化管理

本专题深入解析Nginx基础架构,涵盖从源码编译与包管理安装,到核心配置文件优化及虚拟主机部署。进一步探讨日志轮转、性能调优、高可用集群构建及自动化运维策略,助力管理员实现从单一服务搭建到企业级系统化管理的全面升级,确保Web服务高效、稳定运行。

7

2026.03.17

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 6.4万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号