返回归档
🔂Golang

sync/atomic包

文章目录

是什么

  • 在 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.StoreInt3201 分别表示 falsetrue

使用场景

轻量级的锁-自旋锁

在竞争不频繁或者上下文切换比较的场景下,使用自旋锁,可以减少互斥锁带来的协程阻塞和上下文切换

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)