处理器支持的原子操作

首先我们来讲一下处理器支持的原子操作以及为什么i++不是原子操作

从处理器层面上来讲,处理器保证基本的访存事务的原子性,例如当处理器读取存储器中的一个字节时,在读取过程未结束之前,其他的任何设备都不可以访问这个字节。这个保证对写入字节也成立。但是处理器自动能做的保护也就仅仅如此了。

i++不是原子操作

对于i++这样的操作,其实是分3步执行的,读取i的值,增加i的值,回写i的新值。这3步每一步都是原子操作,但是组合在一起就不一定是原子操作了。可以参看下图中两个线程对i的争抢示例。我们期待线程A与B中的i++可以顺序执行,最终存储器中的结果是2;但是实际上由于两个线程并行执行,结果可能得到1(当然也有可能得到2,运气好的话)。通常i++这样的操作我们称为“读-改-写”操作。

Lock指令前缀

由于的确有业务逻辑要求多线程中类似于i++这样的“读-改-写”操作是原子性操作。处理器因此为软件(即程序员/编译器)提供了一种原生的总线封锁机制,即lock指令前缀。当生成的代码前有lock前缀时,例如伪代码lock i++,则整条指令在访存/修改变量/回写期间,别的设备/代码都不可以触碰被锁住的变量。从上面的例子来看就是,一旦线程A进入load i阶段,线程B就不可以再执行load i。线程B会阻塞,一直等待到线程A的store i完成,才能继续往下执行load i(此时读取到的是线程A的结果i=1),i++, store i,最后将i=2写回到内存中。参看下图。


有了lock指令前缀,程序的正确性问题解决了。但是同时也带了性能问题,其中一个线程不得不等待,直到另一个线程释放“锁”。
出现需要加锁的情况的本质原因是数据变量在多线程之间被共享且发生修改。所以改进程序算法,尽量避免数据共享才是终极解决办法。但是有时候业务逻辑不得不要求数据共享从而必须加锁。程序的正确性是刚需,性能与锁不可兼得。

注:lock前缀在没有高速缓存的机器上让处理器封锁总线,有高速缓存的机器上做cache lock操作,总体目标是保证指令原子性执行。

C/C++中的volatile关键字

Volatile这个关键字修饰变量的时候,本质上是禁止编译器将该变量优化到寄存器中(因为这个变量可能被其他线程修改),这样处理器每次使用这个变量值的时候都会从存储器中读取(当然可能命中高速缓存)。这个关键字只作用在编译器层面,处理器并不感知volatile的存在与作用。

​​​
结论
用volatile保证原子操作,只是确保了第一步load i一定从存储器中读取数据(也可能命中缓存),但是一定不会是某个寄存器。无法保障连续3步读改写的原子性。所以使用lock才是唯一正确的选择。而且,使用了lock前缀之后,也就无需再使用volatile关键字了。

GCC的内嵌原子操作函数

GCC实现了如下的instrinsic function,程序员无需内嵌汇编即可实现原子i++操作。

下面6个函数对*ptr执行操作,返回操作前原*ptr的值。

  1. type __sync_fetch_and_add (type *ptr, type value, ...)
  2. type __sync_fetch_and_sub (type *ptr, type value, ...)
  3. type __sync_fetch_and_or (type *ptr, type value, ...)
  4. type __sync_fetch_and_and (type *ptr, type value, ...)
  5. type __sync_fetch_and_xor (type *ptr, type value, ...)
  6. type __sync_fetch_and_nand (type *ptr, type value, ...)

下面6个函数对*ptr执行操作,返回操作后新*ptr的值。

  1. type __sync_add_and_fetch (type *ptr, type value, ...)
  2. type __sync_sub_and_fetch (type *ptr, type value, ...)
  3. type __sync_or_and_fetch (type *ptr, type value, ...)
  4. type __sync_and_and_fetch (type *ptr, type value, ...)
  5. type __sync_xor_and_fetch (type *ptr, type value, ...)
  6. type __sync_nand_and_fetch (type *ptr, type value, ...)

下面2个函数执行比较并交换操作。第一个bool版本返回true,如果交换成功;第二个val版本返回交换前的oldval值。

  1. bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
  2. type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

下面这个函数会产生一个存储器屏障。

更多内容参看:

  1. https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
  2. https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/_005f_005fatomic-Builtins.html

【C/C++】深入理解原子操作 volatile i为什么不能保证i++的原子性相关推荐

  1. 深入理解原子操作-底层基础

    目录 上锁的原子操作(LOCKED ATOMIC OPERATIONS) 1. 可靠原子锁 2. 总线锁 2.1 自动锁 2.2 软件控制的总线锁 3. 处理自操作和交叉修改代码(Handling S ...

  2. 面试精讲之面试考点及大厂真题 - 分布式专栏 18 谈谈怎么理解幂等,接口如何保证幂等

    18谈谈怎么理解幂等,接口如何保证幂等 时间像海绵里的水,只要你愿意挤,总还是有的. --鲁迅 引言 稳定性设计第一篇:这一小节开始讲设计系统稳定性保证的相关设计,谁都不想自己负责的系统三天两头就出故 ...

  3. volatile不能保证原子性,atomic不仅保证可见性还有原子性CAS分析

    给一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的.可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见.volatile似乎是有时候可以 ...

  4. volite java_如何理解 JAVA volatile 关键字

    最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂.所以学习过程中顺手翻译下来,一方面巩固知识,一方面希 ...

  5. 【Java 并发编程】线程简介 ( 原子操作 | volatile 关键字使用场景 )

    文章目录 一.原子操作 二.volatile 关键字使用场景 一.原子操作 原子操作 : read : 从 主内存 中的线程共享变量中读取数据 ; load : 将从主内存读取到的数据 , 加载到 线 ...

  6. linux 内核同步--理解原子操作、自旋锁、信号量(可睡眠)、读写锁、RCU锁、PER_CPU变量、内存屏障

    内核同步 内核中可能造成并发的原因: 中断–中断几乎可以在任何时刻异步发生,也就可以随时打断当前正在执行的代码. 软中断和tasklet–内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在 ...

  7. volatile 关键字是如何保证可见性的?

    我们可以使用[hsdis]这个工具,来查看前面演示的这段代码的汇编指令,具体的使用请查看使用说明文档 在运行的代码中,设置jvm参数如下 [-server -Xcomp -XX:+UnlockDiag ...

  8. 深入理解Memory Order

    深入理解Memory Order cpu 保证 cache 编程技术 lock-free wait-free Read–modify–write Compare-And-Swap(CAS) cas原理 ...

  9. java的jmm模型_【深入理解JVM】:Java内存模型JMM

    多任务和高并发的内存交互 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标 ...

最新文章

  1. Linux第十一周作业
  2. Neural-RGBD: 从单目视频序列中估计深度及其不确定度
  3. ML之LoRBaggingRF:依次利用Bagging、RF算法对泰坦尼克号数据集 (Kaggle经典案例)获救人员进行二分类预测——模型融合
  4. @valid 不生效_黑帽seo高手-实战细谈301生效周期+影响收录重要因素(探索)
  5. 卷积神经网络(卷积层,激活函数Relu,池化层,计算公式及API解释)
  6. (1)dotnet开源电商系统-brnshopbrnMall 和老外开发的nopCommerce(dotnet两套电商来PK--第一篇)...
  7. c语言编写网页图形界面代码,「分享」C语言如何编写图形界面
  8. 【NLP】毕设学习笔记(四)bert相关知识点
  9. 杭州滨江工作方案:将区块链等产业与“数字滨江”、“数字经济”紧密相连
  10. C#在控制台输出异常所在的行数
  11. 【大数据部落】R在GIS中用ggmap地理空间数据分析
  12. 2017年云南职称计算机考试,云南省2017年职称计算机考试内容及考试方式
  13. 《调色师手册:电影和视频调色专业技法(第2版)》——拍摄之前:选择录制格式...
  14. 简单神经网络手算笔记
  15. 概念区分:灰度发布、蓝绿发布、滚动发布
  16. python发送邮件群发软件_python使用tkinter写的邮件群发软件-python图形界面编程
  17. java复数类实部_Java编写一个复数类Complex,具有实部、虚部成员变量,可以完成加、减、乘、除和获得实部和虚部的方法...
  18. [ 代码审计篇 ] 代码审计思路 详解
  19. LightGCN: Simplifying and Powering Graph Convolution Network for Recommendation【论文阅读笔记】
  20. 32位的md5校验程序

热门文章

  1. leetcode206题:反转链表(迭代或是递归)
  2. 运行opencv保存视频时出现错误的解决方法
  3. vb怎么判断整数_VB数学函数大全
  4. php关联数组json,使用JSON从PHP到Javascript的关联数组
  5. java设置按钮调用问题_按钮相关问题:尝试在空对象引用上调用虚方法
  6. 计算机班服设计图片大全,有创意的班服设计图片,班服图案图片,霸气班服logo图案大全...
  7. java基础知识点(3)——标识符常量变量
  8. 24c04硬件地址位_硬件刷题篇(一)
  9. 大数据— Hadoop
  10. w ndows2000,华塑CAE软件简介