likes
comments
collection
share

Go标准库|atomic包是如何保证并发安全的?

作者站长头像
站长
· 阅读数 30

Go标准库 | atomic包是如何保证并发安全的?

人们总是认为自己没走过的路上开满了鲜花

在Go语言中,sync/atomic包提供了一些原子操作,用于在多个goroutine之间安全地处理数据,而无需使用互斥锁(mutex)。这些操作通过硬件支持的原子指令来保证并发安全,从而避免了数据竞争问题。

什么是原子操作

原子操作是指那些不可被中断的操作,即在操作过程中不会被其他操作打断。原子操作要么全部执行完毕,要么完全不执行,不会出现中间状态。

atomic包中的主要函数

sync/atomic包提供了对各种数据类型的原子操作函数,例如:

  • atomic.AddInt32 / atomic.AddInt64
  • atomic.LoadInt32 / atomic.LoadInt64
  • atomic.StoreInt32 / atomic.StoreInt64
  • atomic.CompareAndSwapInt32 / atomic.CompareAndSwapInt64
  • atomic.SwapInt32 / atomic.SwapInt64

此外,还有针对uint32, uint64, uintptr, unsafe.Pointer类型的类似函数, 详细使用方法请查阅Go源码。

atomic并发安全原理

  1. CPU级别的原子性

现代CPU提供了一些原子指令,这些指令能够保证在多个CPU核心之间操作的原子性。如Compare-And-Swap(CAS)、加载链接 Load-linked (LL)、存储条件Store-conditional (SC)等,这些指令可以保证在执行过程中不会被其他线程或处理器中断。sync/atomic 包中的函数底层使用了这些硬件指令来实现原子操作。

  1. 内存屏障(Memory Barriers)

原子操作不仅要保证操作本身的原子性,还要确保内存操作的顺序性。这是通过内存屏障来实现的。内存屏障是一种防止CPU乱序执行内存操作的机制,它确保在屏障之前的所有操作在屏障之前完成,而在屏障之后的所有操作在屏障之后开始。atomic包使用内存屏障来确保操作顺序性。

  1. Go运行时和编译时优化

Go编译器和运行时系统对原子操作进行了优化,确保这些操作在多核处理器上执行时不会引入数据竞争。编译器能够识别出sync/atomic包中的原子操作,并生成适当的机器指令来实现这些操作。运行时系统也会利用平台特性,确保这些指令以最高效的方式执行。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var counter int64
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            for j := 0; j < 1000; j++ {
                atomic.AddInt64(&counter, 1)
            }
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println("Counter:", counter)
}

在这个例子中,多个goroutine并发地增加counter变量,通过使用atomic.AddInt64,我们保证了对counter变量的更新是原子性的,从而避免了数据竞争问题。

总结

Go语言的sync/atomic通过使用底层的CPU原子指令和内存屏障来保证并发安全。这些原子操作提供了一种高效的方式来实现多goroutine之间的同步,避免了使用互斥锁带来的性能开销。然而,sync/atomic包通常只适用于简单的数据类型(如整数、指针等),对于复杂的数据结构或需要长时间持有的锁,使用互斥锁或其他同步机制可能是更好的选择。

转载自:https://juejin.cn/post/7373202427027652642
评论
请登录