是什么
- 在 Go 语言中,
sync/atomic包提供了底层的原子操作,用于对整数类型和指针类型进行原子性的读写和修改 - 原子操作是不可分割的操作,确保多个 goroutine 并发访问共享数据时,操作不会被中断
CPU指令级别的支持
现代CPU架构提供了一组专门的指令,用于支持原子操作。这些指令能够在单个CPU周期内完成对某些内存操作的读取-修改-写入,这使得这些操作不可分割。也就是说,一旦CPU开始执行这个操作,它会确保整个操作在完成之前不会被其他操作中断。
例如,典型的原子操作指令包括:
CMPXCHG****(Compare-and-Swap,比较并交换):比较一个内存位置的值和一个给定的值,如果相等,则将其更改为新值。XADD****(Exchange and Add,交换并加):将一个数加到一个内存位置的值中,同时返回原来的值。
由于这些操作是在硬件层面直接实现的,它们在执行时能够确保操作的原子性,不需要使用锁来防止数据竞争。
常用函数
sync/atomic 包提供了一系列针对不同数据类型的原子操作函数。下面列出了一些常用的函数:
- 整数操作
atomic.AddInt64(&addr, delta int64) int64:对int64类型的值执行加法操作,返回新值。atomic.LoadInt64(&addr) int64:原子地加载int64类型的值。atomic.StoreInt64(&addr, val int64):原子地存储int64类型的值。atomic.SwapInt64(&addr, new int64) int64:原子地交换int64类型的值,返回旧值。atomic.CompareAndSwapInt64(&addr, old, new int64) bool:同上,但用于int64。
- 指针操作
atomic.LoadPointer(&addr) unsafe.Pointer:原子地加载指针值。atomic.StorePointer(&addr, val unsafe.Pointer):原子地存储指针值。atomic.SwapPointer(&addr, new unsafe.Pointer) unsafe.Pointer:原子地交换指针值,返回旧值。atomic.CompareAndSwapPointer(&addr, old, new unsafe.Pointer) bool:原子地比较并交换指针值。
- 布尔值操作
- Go 并没有专门的
atomic布尔操作函数,但可以通过整数操作来模拟布尔值操作。例如,使用atomic.StoreInt32将0和1分别表示false和true。
- Go 并没有专门的
使用场景
轻量级的锁-自旋锁
在竞争不频繁或者上下文切换比较重的场景下,使用自旋锁,可以减少互斥锁带来的协程阻塞和上下文切换
var lock uint32
func spinLock() {
//尝试加锁,将无锁状态转为有锁
for !atomic.CompareAndSwapUint32(&lock, 0, 1) {
//加锁失败
//自旋等待
}
//临界区域
//handler....
//释放锁
atomic.StoreUint32(&lock, 0)
}
并发场景下的标识符
使用 atomic 可以避免使用锁
var isRunning int32
// 设置状态为正在运行
atomic.StoreInt32(&isRunning, 1)
// 检查状态
if atomic.LoadInt32(&isRunning) == 1 {
// 已经在运行
}
指针交换
var ptr unsafe.Pointer
// 更新指针
oldPtr := atomic.SwapPointer(&ptr, unsafe.Pointer(newValue))
实现并发场景下的计数
var counter int64
// 多个 goroutine 对 counter 进行递增操作
atomic.AddInt64(&counter, 1)