Go原子操作包详解
约 1658 字大约 6 分钟
goatomic
2025-04-21
概述
sync/atomic 包提供了底层的原子内存操作,是构建高性能并发数据结构的基础。原子操作直接映射到CPU的原子指令(如x86的LOCK前缀指令、ARM的LDREX/STREX),保证操作的不可分割性,无需使用互斥锁。Go 1.19引入了更加类型安全的原子类型,进一步提升了使用体验。
基本原子操作
Load / Store
import "sync/atomic"
var counter int64
// 原子读取
val := atomic.LoadInt64(&counter)
// 原子存储
atomic.StoreInt64(&counter, 42)
// 为什么需要原子Load/Store?
// 在某些CPU架构上,64位值的读写不是原子的
// 例如32位ARM上,int64的读写会分为两次32位操作Add
// 原子加法
atomic.AddInt64(&counter, 1) // counter++
atomic.AddInt64(&counter, -1) // counter--
// 获取加法后的值
newVal := atomic.AddInt64(&counter, delta)CompareAndSwap (CAS)
// CAS: 当前值等于old时,替换为new,返回是否成功
swapped := atomic.CompareAndSwapInt64(&counter, oldVal, newVal)
// CAS是构建无锁数据结构的基础
// 底层对应CPU指令:x86的CMPXCHG, ARM的LDREX/STREXSwap
// 原子交换,返回旧值
oldVal := atomic.SwapInt64(&counter, newVal)CAS 自旋模式
// 使用CAS实现无锁计数器
func increment(counter *int64) {
for {
old := atomic.LoadInt64(counter)
if atomic.CompareAndSwapInt64(counter, old, old+1) {
return // CAS成功,退出
}
// CAS失败(被其他goroutine修改了),重试
}
}
// 使用CAS实现无锁栈
type LockFreeStack struct {
top unsafe.Pointer // *node
}
type node struct {
value interface{}
next unsafe.Pointer // *node
}
func (s *LockFreeStack) Push(v interface{}) {
n := &node{value: v}
for {
top := atomic.LoadPointer(&s.top)
n.next = top
if atomic.CompareAndSwapPointer(&s.top, top, unsafe.Pointer(n)) {
return
}
}
}
func (s *LockFreeStack) Pop() (interface{}, bool) {
for {
top := atomic.LoadPointer(&s.top)
if top == nil {
return nil, false
}
n := (*node)(top)
if atomic.CompareAndSwapPointer(&s.top, top, n.next) {
return n.value, true
}
}
}atomic.Value
atomic.Value 提供了任意类型值的原子读写:
var config atomic.Value
// 存储配置
type Config struct {
Workers int
Debug bool
}
config.Store(&Config{Workers: 8, Debug: false})
// 原子读取配置
cfg := config.Load().(*Config)
fmt.Println(cfg.Workers) // 8
// 典型用法:配置热更新
go func() {
for range ticker.C {
newCfg := loadConfigFromFile()
config.Store(newCfg)
}
}()
// 在请求处理中读取(无锁)
func handleRequest() {
cfg := config.Load().(*Config)
// 使用cfg...
}注意事项:
- 第一次Store后,后续Store必须存储相同类型的值
- Store nil会panic
- 内部使用unsafe.Pointer实现
Go 1.19+ 新原子类型
Go 1.19引入了一组泛型化的原子类型,提供更好的类型安全和可读性:
// atomic.Int32 / atomic.Int64 / atomic.Uint32 / atomic.Uint64
var counter atomic.Int64
counter.Store(0)
counter.Add(1)
val := counter.Load()
swapped := counter.CompareAndSwap(1, 2)
// atomic.Bool
var ready atomic.Bool
ready.Store(true)
if ready.Load() {
// ...
}
// atomic.Pointer[T](Go 1.19+)
type Config struct{ Debug bool }
var configPtr atomic.Pointer[Config]
configPtr.Store(&Config{Debug: true})
cfg := configPtr.Load()
fmt.Println(cfg.Debug) // true, 无需类型断言
// atomic.Uintptr
var addr atomic.Uintptr新旧API对比
// 旧API(Go 1.18及之前)
var counter int64
atomic.AddInt64(&counter, 1)
val := atomic.LoadInt64(&counter)
// 新API(Go 1.19+)
var counter atomic.Int64
counter.Add(1)
val := counter.Load()
// 优势:
// 1. 方法调用,不需要取地址
// 2. 不能被意外非原子访问(封装了底层值)
// 3. 更好的代码可读性
// 4. atomic.Pointer[T] 提供类型安全的指针操作内存序(Memory Ordering)
Go的原子操作提供顺序一致性(sequential consistency)保证:
// Go的atomic操作保证:
// 1. 原子操作之间的全局顺序一致
// 2. 不会被编译器或CPU重排到其他原子操作之前/之后
// 3. 类似C++的 memory_order_seq_cst
// 例子:确保happens-before关系
var (
data [100]int
ready atomic.Bool
)
// Goroutine 1
func writer() {
for i := range data {
data[i] = i // 先写数据
}
ready.Store(true) // 后设置标志(释放语义)
}
// Goroutine 2
func reader() {
for !ready.Load() { // 等待标志(获取语义)
runtime.Gosched()
}
// 此处保证能看到data的所有写入
fmt.Println(data[99]) // 保证输出99
}Mutex vs Atomic 对比
// Benchmark: 无竞争场景
// atomic.Add: ~5 ns/op
// mutex Lock/Unlock: ~15 ns/op
// Benchmark: 高竞争场景 (8 goroutines)
// atomic.Add: ~50 ns/op (CAS retry)
// mutex Lock/Unlock: ~200 ns/op (上下文切换)
// 选择依据:
// - 简单的计数器/标志位:atomic
// - 需要保护多个变量的一致性:mutex
// - 需要条件等待:mutex + cond 或 channel
// - 读多写少的配置:atomic.Value 或 atomic.Pointer| 特性 | atomic | mutex |
|---|---|---|
| 粒度 | 单个变量 | 任意临界区 |
| 开销 | CPU指令级 (~5ns) | 系统调用级 (~15ns) |
| 阻塞 | 自旋(不让出CPU) | 可能让出CPU |
| 饥饿 | 可能(CAS自旋) | Go 1.9+有饥饿模式 |
| 复杂度 | 仅限简单操作 | 支持复杂逻辑 |
常见使用场景
// 1. 并发计数器
type Metrics struct {
requests atomic.Int64
errors atomic.Int64
}
func (m *Metrics) RecordRequest() { m.requests.Add(1) }
func (m *Metrics) RecordError() { m.errors.Add(1) }
// 2. 发布-订阅标志
type Service struct {
shutdown atomic.Bool
}
func (s *Service) Shutdown() { s.shutdown.Store(true) }
func (s *Service) IsRunning() bool { return !s.shutdown.Load() }
// 3. 单次初始化标志(比sync.Once更灵活)
type LazyInit struct {
done atomic.Bool
mu sync.Mutex
data *Data
}
func (l *LazyInit) Get() *Data {
if l.done.Load() {
return l.data
}
l.mu.Lock()
defer l.mu.Unlock()
if !l.done.Load() {
l.data = expensiveInit()
l.done.Store(true)
}
return l.data
}
// 4. 无锁配置热更新
var currentConfig atomic.Pointer[Config]
func UpdateConfig(cfg *Config) {
currentConfig.Store(cfg) // 一次原子写入
}
func GetConfig() *Config {
return currentConfig.Load() // 一次原子读取
}注意事项
// 1. 64位原子操作在32位平台上需要8字节对齐
// Go 1.19+的atomic类型自动保证对齐
// 旧API中struct里的int64/uint64需要注意
type BadStruct struct {
flag bool // 1 byte
counter int64 // 可能未对齐!
}
// 解决:将64位字段放在struct前面
type GoodStruct struct {
counter int64 // 保证8字节对齐
flag bool
}
// 2. 不要混合原子和非原子访问同一变量
// 3. atomic.Value的Store类型不能变
// 4. CAS在高竞争下可能导致活锁(考虑退避策略)总结
| API | 用途 | Go 1.19+替代 |
|---|---|---|
| atomic.LoadInt64 | 原子读取 | atomic.Int64.Load |
| atomic.StoreInt64 | 原子存储 | atomic.Int64.Store |
| atomic.AddInt64 | 原子加法 | atomic.Int64.Add |
| atomic.CompareAndSwapInt64 | CAS | atomic.Int64.CompareAndSwap |
| atomic.Value | 任意类型原子值 | atomic.Pointer[T] |
原子操作是Go并发工具箱中最底层的工具。在简单场景下,它比mutex更高效;但在复杂场景下,mutex更安全、更易理解。优先使用Go 1.19+的新原子类型,享受更好的类型安全和对齐保证。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于