Go atomic
一、介绍
atomic 是 Go 标准库中用于实现原子操作 的包,核心解决多 goroutine 并发下的变量竞态问题,比互斥锁(sync.Mutex)更轻量、性能更高。
二、原子操作
2.1 介绍
原子操作是指不可被中断的操作:要么完全执行完毕,要么完全不执行,中间不会被其他goroutine抢占。
2.2 底层原理
atomic.AddInt32(&x, 1) 是一步完成的,底层依赖 CPU 指令(如 LOCK XADD),通过锁定内存总线/ 缓存行,确保同一时刻只有一个CPU核心操作该内存地址,保证原子性,无竞态风险
三、适用场景
- 轻量级的计数(eg 请求数、并发数统计)
- 状态标记(eg 服务是否启动 任务是否完成)
- 高性能的并发变量更新(替代互斥锁、减少上下文切换)
四、核心功能
atomic 主要操作基本数值类型 (int32/int64/uint32/uint64/uintptr)和指针类型 (Pointer)
ps :atomic 操作的对象是一个地址 ,需要传递地址
4.1 Add
对数值类型进行原子增减,给第一个参数地址中的值增加一个 delta 值
- 变量必须是指针类型(因为要直接操作内存)
- uint 类型加法:若要实现减法,需通过 atomic.AddUint32(&x, ^uint32(n-1))(利用补码),例如 atomic.AddUint32(&x, ^uint32(0)) 等价于 x--
go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var count int32 = 0 // 必须是int32/int64等原子支持的类型
var wg sync.WaitGroup
// 启动1000个goroutine,每个goroutine对count加1
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 原子加1:第一个参数是变量指针,第二个是增量(负数表示减)
atomic.AddInt32(&count, 1)
}()
}
wg.Wait()
fmt.Println("最终计数:", count) // 输出 1000(无竞态)
}
4.2 加载操作(Load)
原子读取变量值,保证读取到的是最新值(避免编译器 / CPU 缓存导致的可见性问题)
4.3 存储操作(Store)
原子写入变量值,保证写入的原子性和可见性(其他 goroutine 能立即看到最新值)
替代普通赋值 x = 0,避免并发下的写覆盖问题
go
package main
import (
"fmt"
"sync/atomic"
"time"
)
var status int32 = 1
func getStatus() {
fmt.Println(atomic.LoadInt32(&status))
}
func stop() {
atomic.StoreInt32(&status, 0)
}
func main() {
go getStatus()
go stop()
time.Sleep(time.Second)
}
结果可能是1也可能是0
4.4 比较并交换(CAS)
go
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
方法逻辑:
- 检查
addr指向的变量值是否等于old; - 若相等,将其更新为
new,返回true; - 若不相等,不做修改,返回
false。
CAS 是乐观锁思想,无阻塞但可能重试;
Go 中 sync/atomic 的 CAS 是硬件级别的支持,性能远高于互斥锁;
go
package main
import (
"fmt"
"sync/atomic"
"time"
)
var status int32 = 1
func incr() {
for {
old := atomic.LoadInt32(&status)
new := old + 1
// CAS操作:只有old等于当前值时才更新
if atomic.CompareAndSwapInt32(&status, old, new) {
fmt.Println("status:", atomic.LoadInt32(&status))
break
}
}
}
func main() {
go incr()
time.Sleep(time.Second)
}
4.5 交换操作(Swap)
无条件原子替换变量值,返回旧值,eg: 强制重置计数器
4.5 atomic.Value
支持`任意类型的原子读写
ps :
atomic.Value存储的类型必须固定(比如第一次存 map,后续不能存 int)- 存储引用类型(如 map、slice)时,必须拷贝后修改,避免并发读写原对象
go
package main
import (
"bytes"
"fmt"
"strconv"
"sync"
"sync/atomic"
"time"
)
func main() {
var date atomic.Value
// 1. 存储数据
date.Store(map[string]time.Time{"time1": time.Now()})
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 2. 加载数据
oldMap := date.Load().(map[string]time.Time)
// 3. 拷贝旧数据(避免修改原数据)
newMap := make(map[string]time.Time)
for k, v := range oldMap {
newMap[k] = v
}
buf := bytes.NewBuffer([]byte{})
buf.WriteString("time")
buf.WriteString(strconv.Itoa(i))
newMap[buf.String()] = time.Now()
date.Store(newMap)
}()
}
wg.Wait()
fmt.Println(len(date.Load().(map[string]time.Time)))
fmt.Println(date.Load().(map[string]time.Time))
fmt.Println("main done")
}
五、与mutex比较
| 特性 | atomic | sync.Mutex |
|---|---|---|
| 适用场景 | 单个变量的原子操作 (如计数、状态标记) | 复杂逻辑 / 多个变量的原子操作(比如转账(扣减 A 账户 + 增加 B 账户)) |
| 性能 | 极高(硬件指令级) | 较低(有上下文切换开销) |
| 使用复杂度 | 简单(仅支持基本操作) | 灵活(支持任意逻辑) |
| 竞态保护 | 仅保护变量读写 | 保护代码块 |