CPU中的原子操作

版权声明:本文为博主原创文章,有任何错误请大家批评指正,未经博主允许不得转载。 https://blog.csdn.net/CringKong/article/details/79966161

今天在学习多线程时突然想到一个问题,CPU的CAS操作保证了原子性,但是现在的CPU都是多核心可以并行运算的多核CPU了,那CPU怎么实现并行运算时而又能保证对内存操作的原子性呢?

于是查阅了一些资料,有了以下了解:

所谓原子操作,就是”不可中断的一个或一系列操作”

1.单核CPU的原子操作

在单核CPU中, 能够在一个指令中完成的操作都可以看作为原子操作, 因为中断只发生在指令间.

也就是说单核CPU由于不存在多核心并行运算的情况,所有的操作其实都可以视为是原子的,就相当于我们如果只有一只手,就不能一边打字一边抠脚了,所以打字和抠脚的行为,变成了串行的行动,不能并行进行,所以都是原子性操作。

2.多核CPU的原子操作

典型的例子就是decl指令(递减指令), 它细分为三个过程: “读->改->写”, 涉及两次内存操作. 如果多个CPU运行的多个进程在同时对同一块内存执行这个指令, 那情况是无法预测的.

可以看到多核CPU的情况下,就会出现类似我们多线程中出现的问题,对于同一块内存中的变量,多个核心同时读写修改数据的话,就会出现不可预期的错误。

那么CPU是怎么解决这个问题的呢?其实和我们软件平台实现思路很像,就是使用锁机制(回想一下JAVA中的同步锁)

软件级别的原子操作是依赖于硬件支持的. 在x86体系中, CPU提供了HLOCK pin引线, 允许CPU在执行某一个指令(仅仅是一个指令)时拉低HLOCK pin引线的电位, 直到这个指令执行完毕才放开. 从而锁住了总线, 如此在同一总线的CPU就暂时无法通过总线访问内存了, 这样就保证了多核处理器的原子性. (想想这机制对性能影响挺大的).

通俗来讲就是你在被警察抓住了以后,一只手被手铐锁住,就只有另一只手能做事了,也就是说在执行原子操作时,CPU保证只有一个核心能对内存进行操作,这样以来就不会出现多个核心同时运算并回写内存的情况了

3.哪些操作可以确定为原子操作了?

对于非long和double基本数据类型的”简单操作”都可以看作是原子的. 例如: 赋值和返回. 大多数体系中long和double都占据8个字节, 操作系统或者JVM很可能会将写入和读取操作分离为两个单独的32位的操作来执行, 这就产生了在一个读取和写入过程中一个上下文切换(context switch), 从而导致了不同任务线程看到不正确结果的的可能性.

递增, 递减不是原子操作: i++反汇编的汇编指令: (需要三条指令操作, 和两个内存访问, 一次寄存器修改)

// x86指令体系
movl i, %eax                            //内存访问, 读取i变量的值到cpu的eax寄存器
addl $1, %eax                         //增加寄存器中的值
movl %eax, i                            //写入寄存器中的值到内存

问:如何实现x++的原子性?

在单处理器上,如果执行x++时,禁止多线程调度,就可以实现原子。因为单处理的多线程并发是伪并发。
在多处理器上,需要借助cpu提供的Lock功能。锁总线。读取内存值,修改,写回内存三步期间禁止别的CPU访问总线。同时我估计使用Lock指令锁总线的时候,OS也不会把当前线程调度走了。要是调走了,那就麻烦了。

最后,我的一些理解和想法:

  1. 对于CAS操作,编译器会自动汇编成CPU可以实现原子操作的汇编代码(不同CPU下实现可能不同,但是一定会借助某些锁实现汇编代码块的原子性)。
  2. 使用CAS实现无锁同步的优点:减少操作系统阻塞线程的负担,直接使用硬件级别的锁必然要比在其之上的系统调用(比如OS实现的mutex互斥锁)更节省资源。

参考资料:

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页