CPU中的原子操作

今天在学习多线程时突然想到一个问题,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互斥锁)更节省资源。

参考资料:

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是IAR提供的一些常用的原子操作代码: 1. 原子读操作: ```c uint32_t __ldrex(volatile uint32_t *addr) ``` 使用示例: ```c volatile uint32_t shared_var = 0; uint32_t read_var; read_var = __ldrex(&shared_var); ``` 2. 原子写操作: ```c uint32_t __strex(uint32_t val, volatile uint32_t *addr) ``` 使用示例: ```c volatile uint32_t shared_var = 0; uint32_t write_var = 123; if (__strex(write_var, &shared_var) == 0) { // 写入成功 } else { // 写入失败 } ``` 3. 原子清除操作: ```c void __clrex(void) ``` 使用示例: ```c volatile uint32_t shared_var = 0; uint32_t read_var; read_var = __ldrex(&shared_var); // 使用共享变量 __clrex(); // 清除同步标志位,允许其他CPU/断修改共享变量 ``` 4. 数据内存屏障操作: ```c void __dmb(void) ``` 使用示例: ```c volatile uint32_t shared_var = 0; uint32_t read_var; read_var = __ldrex(&shared_var); // 使用共享变量 __dmb(); // 确保内存访问的顺序性 ``` 5. 指令内存屏障操作: ```c void __isb(void) ``` 使用示例: ```c volatile uint32_t shared_var = 0; uint32_t read_var; read_var = __ldrex(&shared_var); // 使用共享变量 __isb(); // 清空流水线,从新的地址开始执行 ``` 以上是一些常用的IAR原子操作代码,可以帮助我们实现多线程编程或者断处理对共享资源的访问控制。需要注意的是,这些指令只能用于32位数据类型的操作。如果需要对其他数据类型进行操作,可以使用其他类型的原子指令,例如8位或者16位原子指令。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值