如何使用Golang sync/atomic实现原子操作_Golang atomic并发示例

发布时间 - 2025-12-31 00:00:00    点击率:
不能直接用++或=更新共享计数器,因为++是非原子的读-改-写操作,多goroutine并发时会导致数据竞争和结果错误;必须使用sync/atomic(如atomic.AddInt64)或sync.Mutex保证线程安全。

为什么不能直接用 ++= 更新共享计数器

多个 goroutine 同时对一个 int 变量执行 counter++,结果大概率小于预期值。这不是“偶尔出错”,而是根本没定义行为:++ 是读-改-写三步操作,中间可能被抢占,导致覆盖彼此的写入。Go 编译器和 CPU 都不保证其原子性,即使变量是全局或指针指向的也不行。

  • 典型现象:go run -race 会报 Data Race;不加 -race 也可能跑出随机结果
  • 不是线程安全的替代方案:sync.Mutex 能解决问题,但有锁开销;而简单计数、标志位切换等场景,sync/atomic 更轻量
  • 必须用指针传入:atomic 系列函数操作的是内存地址,所有参数类型都要求是 *T

atomic.AddInt64atomic.LoadInt64 配合使用最常见

计数器增减 + 读取是原子操作最典型的组合。注意:所有整数原子操作都严格区分有符号/无符号、32/64 位,不能混用类型,否则编译失败或 panic。

var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

func get() int64 {
    return atomic.LoadInt64(&counter)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println("final:", get()) // 总是 100
}
  • atomic.AddInt64 返回新值(可选),atomic.LoadInt64 是唯一安全读取方式
  • 不要用 counter++ 替代 atomic.AddInt64(&counter, 1),哪怕只有一处也破坏原子性
  • 32 位系统上 int64 原子操作要求地址 8 字节对齐,Go 运行时通常保证,但若手动构造结构体字段顺序不当,可能触发 panic

atomic.CompareAndSwapInt32 实现无锁状态机

当需要“仅在满足某条件时才更新”(比如初始化一次、状态从 idle 切到 running),CAS 是核心原语。它比锁更细粒度,且天然支持乐观并发控制。

const (
    stateIdle = iota
    stateRunning
    stateDone
)

var state int32 = stateIdle

func startWork() bool {
    return atomic.CompareAndSwapInt32(&state, stateIdle, stateRunning)
}

func finishWork() {
    atomic.StoreInt32(&state, stateDone)
}

func getState() int32 {
    return atomic.LoadInt32(&state)
}
  • CompareAndSwap 返回 bool:成功则返回 true,失败不修改值
  • 必须用 int32(或 uint32)配合 CASint64 版本在 32 位平台需要额外指令支持,性能略低
  • 别写成 if state == stateIdle { state = stateRunning } —— 这中间存在竞态窗口,CAS 才是正确抽象

指针和结构体的原子操作要格外小心

atomic.Pointer[T](Go 1.19+)可用于原子替换指针,但 atomic.Value 更适合存储任意类型数据(如配置快照)。二者都不支持对结构体字段做部分更新 —— 原子操作只能针对整个值。

  • atomic.Pointer[*MyStruct] 可以安全地替换整个指针,但无法原子修改 p.Load().Field = x
  • atomic.ValueStore/Load 是类型安全的,但内部用反射,有轻微开销;适合不频繁写、高频读的场景(如全局配置)
  • 没有 atomic.UpdateStringatomic.AddFloat64 —— 浮点数和字符串需靠 Value 或自定义 CAS 循环实现

真正容易被忽略的是:原子操作只保证单个操作的线性化,不构成内存屏障之外的同步语义。如果后续逻辑依赖该原子操作的结果(比如写完 flag 再发消息),往往还需搭配 sync/atomic 提供的 Store/Load 内存序控制,或直接使用 chan / sync.WaitGroup 显式协调。


# go  # golang  # 字节  # ai  # 无锁  # 为什么  # 有锁  # if  # 字符串  # 结构体  # bool  # int  # 循环  # 指针  # 线程  # pointer  # 并发  # 的是  # 都不  # 会报  # 线性化  # 都要  # 多个  # 才是  # 这不是  # 一处  # 自定义 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何创建和注册中间件_Laravel中间件编写与应用流程  如何挑选高效建站主机与优质域名?  laravel怎么配置Redis作为缓存驱动_laravel Redis缓存配置教程  edge浏览器无法安装扩展 edge浏览器插件安装失败【解决方法】  Laravel如何安装使用Debugbar工具栏_Laravel性能调试与SQL监控插件【步骤】  如何快速搭建二级域名独立网站?  Linux网络带宽限制_tc配置实践解析【教程】  如何快速搭建支持数据库操作的智能建站平台?  详解Android——蓝牙技术 带你实现终端间数据传输  教你用AI润色文章,让你的文字表达更专业  Laravel项目怎么部署到Linux_Laravel Nginx配置详解  JavaScript常见的五种数组去重的方式  百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧  网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?  奇安信“盘古石”团队突破 iOS 26.1 提权  JavaScript如何实现音频处理_Web Audio API如何工作?  潮流网站制作头像软件下载,适合母子的网名有哪些?  Laravel如何创建自定义Facades?(详细步骤)  Android Socket接口实现即时通讯实例代码  Laravel如何使用软删除(Soft Deletes)功能_Eloquent软删除与数据恢复方法  php485函数参数是什么意思_php485各参数详细说明【介绍】  如何用虚拟主机快速搭建网站?详细步骤解析  Laravel如何实现数据库事务?(DB Facade示例)  Laravel如何实现RSS订阅源功能_Laravel动态生成网站XML格式订阅内容【教程】  美食网站链接制作教程视频,哪个教做美食的网站比较专业点?  长沙做网站要多少钱,长沙国安网络怎么样?  Java类加载基本过程详细介绍  高端建站三要素:定制模板、企业官网与响应式设计优化  深圳网站制作平台,深圳市做网站好的公司有哪些?  如何在搬瓦工VPS快速搭建网站?  Laravel如何处理CORS跨域问题_Laravel项目CORS配置与解决方案  Laravel如何使用Socialite实现第三方登录?(微信/GitHub示例)  Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全  Win11怎么设置默认图片查看器_Windows11照片应用关联设置  东莞市网站制作公司有哪些,东莞找工作用什么网站好?  Laravel如何与Docker(Sail)协同开发?(环境搭建教程)  Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】  网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?  如何用腾讯建站主机快速创建免费网站?  Laravel如何自定义分页视图?(Pagination示例)  网页制作模板网站推荐,网页设计海报之类的素材哪里好?  Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧  如何在万网开始建站?分步指南解析  Android中Textview和图片同行显示(文字超出用省略号,图片自动靠右边)  MySQL查询结果复制到新表的方法(更新、插入)  Laravel如何实现用户密码重置功能?(完整流程代码)  网站制作价目表怎么做,珍爱网婚介费用多少?  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  如何选择PHP开源工具快速搭建网站?  如何快速启动建站代理加盟业务?