Golang 中的原子操作(atomic)有什么作用?如何使用?

在 Golang 中,原子操作(atomic)用于对整数进行原子读写操作。原子操作可以确保对整数的并发访问是安全的。

atomic 包提供了以下原子操作:

  • AddInt32/AddUint32/AddInt64/AddUint64:原子的整数加操作
  • CompareAndSwapInt32/CompareAndSwapUint32/CompareAndSwapInt64/CompareAndSwapUint64:CAS操作,比较并交换
  • LoadInt32/LoadUint32/LoadInt64/LoadUint64:原子的读取操作
  • StoreInt32/StoreUint32/StoreInt64/StoreUint64:原子的写入操作
  • SwapInt32/SwapUint32/SwapInt64/SwapUint64:原子的交换操作

例如:

var counter int32

func inc() {
    atomic.AddInt32(&counter, 1)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 1000; j++ {
                inc()
            }
        }()
    }
    wg.Wait()
    println(atomic.LoadInt32(&counter))  // 10000
}

这里使用 atomic.AddInt32() 对 counter 进行原子增加,最终结果是正确的 10000。

如果使用普通的读写操作:

var counter int32

func inc() {
    counter++ 
}

// ...

println(counter)  // 结果是未知的

很可能出现竞争条件,最终打印的结果是未知的。

再看一个 CAS 的例子:

var lock bool

func tryLock() bool {
    return atomic.CompareAndSwapInt32(&lock, 0, 1)
}

func unlock() {
    atomic.StoreInt32(&lock, 0)
}

这里实现了一个简单的互斥锁,使用 CAS 操作复原和设置 lock。

所以原子操作主要有以下作用:

  1. 线程安全的改变某个整数值。
  2. 实现简单的锁、互斥量等同步原语。
  3. 避免竞争条件的出现。

虽然 Golang 的 Channel 和 Mutex 可以实现更高级的同步操作,但是原子操作由于其简单高效的特点,在一些场景下也有很好的应用。

所以总结来说,原子操作是 Golang 在语言层面提供的一种轻量级的同步机制。它通过特定的汇编指令实现,可以保证读写操作的原子性。对整数类型有比较广泛的支持,可以实现一些简单的同步原语。