Go标准库|atomic包是如何保证并发安全的?
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并发安全原理
- CPU级别的原子性
现代CPU提供了一些原子指令,这些指令能够保证在多个CPU核心之间操作的原子性。如Compare-And-Swap
(CAS)、加载链接 Load-linked
(LL)、存储条件Store-conditional
(SC)等,这些指令可以保证在执行过程中不会被其他线程或处理器中断。sync/atomic
包中的函数底层使用了这些硬件指令来实现原子操作。
- 内存屏障(Memory Barriers)
原子操作不仅要保证操作本身的原子性,还要确保内存操作的顺序性。这是通过内存屏障来实现的。内存屏障是一种防止CPU乱序执行内存操作的机制,它确保在屏障之前的所有操作在屏障之前完成,而在屏障之后的所有操作在屏障之后开始。atomic
包使用内存屏障来确保操作顺序性。
- 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