在 Go 的并发编程中,我们通常会使用 sync.Mutex 或 sync.RWMutex 来保证线程安全。但在一些简单场景下,其实我们可以使用更轻量、更高性能的方式 —— 原子化操作(atomic operations)。
本文将系统讲清楚:
- 什么是原子化操作
- Go 提供了哪些原子操作
- atomic 和 mutex 的区别
- 适用场景
- 底层原理
一、什么是原子化操作?
原子操作(Atomic Operation)指的是:
在并发环境中,一个操作不可被打断,要么全部完成,要么完全不执行。
它具备两个核心特性:
- 不可中断
- 并发安全
例如:
count++
在并发环境下其实是 三步操作:
- 读取 count
- +1
- 写回
如果两个 goroutine 同时执行,就会产生数据竞争。
而原子操作会保证这三步作为一个不可分割的整体执行。
二、Go 的原子操作来自哪里?
Go 提供原子操作的标准库是:
sync/atomic
三、Go 支持哪些原子操作?
可以分为 4 大类。
1️⃣ 原子读写(Load / Store)
atomic.LoadInt64(&x)
atomic.StoreInt64(&x, 100)
特点:
- 保证读写不会读到“中间状态”
- 保证内存可见性
- 常用于状态变量
应用场景:
- 服务开关
- 配置热更新
- 运行状态标记
2️⃣ 原子加减(Add)
atomic.AddInt64(&count, 1)
atomic.AddInt64(&count, -1)
这是最常见的用法。
适用于:
- 统计 QPS
- 在线人数
- 请求计数
相比 mutex:
- 更轻量
- 无锁
- 性能更高
3️⃣ CAS 操作(Compare-And-Swap)
atomic.CompareAndSwapInt64(&x, old, new)
只有当当前值等于 old,才更新为 new。
这是无锁编程的核心。
应用场景:
- 状态机控制
- 一次性初始化
- 自旋锁实现
- lock-free 数据结构
4️⃣ 原子指针与 atomic.Value
原子指针
atomic.LoadPointer
atomic.StorePointer
但现在更推荐使用:
atomic.Value
var config atomic.Value
config.Store(newConfig)
cfg := config.Load().(*Config)
适合:
- 配置热更新
- 读多写少场景
优点:
- 整体替换对象
- 并发安全
- 无需加锁
四、支持哪些类型?
Go 的原子操作主要支持:
- int32
- int64
- uint32
- uint64
- uintptr
- unsafe.Pointer
- atomic.Value(任意类型,但必须类型一致)
⚠️ 注意:
不能对 struct 内部字段直接做原子操作
不能对 map / slice 做原子操作
五、atomic 和 mutex 的区别
| 对比项 | atomic | mutex |
|---|---|---|
| 是否加锁 | 否 | 是 |
| 性能 | 高 | 稍低 |
| 复杂逻辑支持 | 不支持 | 支持 |
| 易用性 | 易错 | 更安全 |
| 使用场景 | 简单变量 | 临界区逻辑 |
一句总结:
atomic 适合简单变量的并发安全,mutex 适合复杂逻辑的临界区保护。
六、一个完整示例
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var count int64
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
atomic.AddInt64(&count, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println("count:", count)
}
如果这里使用 count++,就会产生数据竞争。
七、底层原理(进阶)
atomic 的实现依赖:
- CPU 原子指令(如 CAS)
- 内存屏障(Memory Barrier)
它不仅保证操作原子性,还保证:
- 可见性
- 有序性
这也是为什么 atomic 不只是简单的“加锁替代品”。
八、什么时候应该使用 atomic?
适合:
- 简单计数器
- 状态标志
- 读多写少场景
- 无锁优化
不适合:
- 多步骤业务逻辑
- 需要一致性事务操作
- map / slice 复杂修改
九、常见误区
1️⃣ atomic 不是万能的
2️⃣ atomic 不能替代 mutex 所有场景
3️⃣ atomic 组合操作不一定安全
4️⃣ 复杂逻辑一定要用锁
十、总结
Go 的原子操作是一种:
- 轻量
- 高性能
- 无锁
- 适合简单变量并发控制
的并发工具。
但它的能力边界也很清晰:
简单状态用 atomic,复杂逻辑用 mutex。