0

0

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

P粉602998670

P粉602998670

发布时间:2025-12-31 13:09:09

|

262人浏览过

|

来源于php中文网

原创

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

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

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

多个 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 更适合存储任意类型数据(如配置快照)。二者都不支持对结构体字段做部分更新 —— 原子操作只能针对整个值。

sematic
sematic

一个开源的机器学习平台

下载

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

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

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

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

174

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

224

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

335

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

206

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

388

2024.05.21

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

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

193

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

188

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

191

2025.06.17

vlookup函数使用大全
vlookup函数使用大全

本专题整合了vlookup函数相关 教程,阅读专题下面的文章了解更多详细内容。

28

2025.12.30

热门下载

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

精品课程

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

共32课时 | 3.1万人学习

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

共10课时 | 0.8万人学习

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

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