Go中sync/atomic原子操作分析
Go中sync/atomic原子操作分析
ivansli1. 什么是原子操作
程序的原子操作一般指:程序的一条或者几条指令是一个不可分割的执行整体,要么全部执行,要么不执行。
在Go中有原子操作对应的包sync/atomic,可以用来对某些具有竞争性的变量进行原子操作,以免在多goroutine操作的情况下数据发生错乱,而不能达到预期要求。
2. 多goroutine加法的两种操作
普通的加法操作:
1 | func main() { |
执行结果基本都是 num != 100。
使用sync/atomic包进行原子加法操作:
1 | func main() { |
执行结果都为 num == 100。
3. 从汇编层面分析
为什么会出现上面的执行结果呢?
首先分析一下num += add
的汇编代码:
1 | MOVL 0(SP), AX ;获取num的值 |
也就是说,num += add
对应3条底层CPU指令,由于多goroutine是并发执行并没有采取其他同步机制,导致同时会有多个goroutine取到相同的num值,并进行加1操作。这样就导致看到的结果不等于100。
在看一下原子操作atomic.AddUint32(&num, add)
对应的汇编:
1 | MOVQ 0x18(SP), AX ;num的地址放到寄存器AX |
也是3条底层CPU指令,但是与上面的3条CPU指令却有本质的区别。
LOCK XADDL CX, 0(AX)
中LOCK前缀标识该指令为原子操作指令(由硬件进行支持),XADDL表示交换并相加(相当于num += add
对应的3条底层CPU指令)是一个不可分割的原子操作。由于LOCK的加持,就算是多goroutine同时执行到了该CPU指令的位置,也只有一个goroutine能执行成功,其他goroutine都要进行等待。这也就是使用atomic执行加法操作能够达到预期的原因。
4. go源码中AddUint32的实现
1 | // 源码位置:sync/atomic/doc.go |
可以看到原子操作的方法是由汇编代码实现(不同的硬件平台实现原子操作所提供的指令不一样),核心的指令还是 LOCK
与XADDL
。
sync/atomic包的其他方法,也是使用类似的汇编代码实现。
总结
通过上面代码以及汇编分析,可得以下结论:
- 原子操作是不可分割的整体(包含一条或者多条处理动作)
- 不同架构的原子操作,其实现指令不同
- 原子操作需要底层硬件支持(加锁一般由操作系统提供的API支持)
评论
匿名评论
✅ 你无需删除空行,直接评论以获取最佳展示效果