原子操作(atomic)是无锁编程(Lock-Free Programming)的基础。以往,要使用atomic操作,我们一般会使用gcc内置的原子操作接口,或者是基于指定平台硬件指令封装的atomic库。c++11直接引入了atomic库,为c++定义了原子类型操作接口以及内存模型,极大的方便了我们的使用。我尝试通过本文对C++11中内存屏障(内存顺序)的一些基本概念和使用情况进行总结。

C++内存模型可以被看作是C++程序和计算机系统(包括编译器,多核CPU等可能对程序进行乱序优化的软硬件)之间的契约,它规定了多个线程访问同一个内存地址时的语义,以及某个线程对内存地址的更新何时能被其它线程看见。这个模型约定:没有数据竞争(data race)的程序是遵循顺序一致性的。该模型的核心思想就是由程序员用同步原语(例如,锁或者C++11中新引入的atomic类型的共享变量)来保证你程序是没有数据竞争的,这样CPU和编译器就会保证程序是按程序员所想的那样执行的(即顺序一致性)。换句话说,程序员只需要恰当地使用具有同步语义的指令来标记那些真正需要同步的变量和操作,就相当于告诉CPU和编译器不要对这些标记好的同步操作和变量做违反顺序一致性的优化,而其它未被标记的地方可以做原有的优化。编译器和CPU的大部分优化手段都可以继续实施,只是在同步原语处需要对优化做出相应的限制;而且程序员只需要保证正确地使用同步原语即可,因为它们最终表现出来的执行效果与顺序一致性模型一致。由此,C++多线程内存模型帮助我们在易编程性和性能之间取得了一个平衡。

在C++11标准之前,C++是建立在单线程语义上的。为了进行多线程编程,C++程序员通过使用诸如Pthreads,Windows Thread等C++语言标准之外的线程库来完成代码设计。以Pthreads为例,它提供了类似pthread_mutex_lock这样的函数来保证对共享变量的互斥访问,以防止数据竞跑。人们不禁会问,Pthreads这样的线程库我用的好好的,干嘛需要C++引入的多线程,这不是多此一举么?其实,以线程库的形式进行多线程编程在绝大多数应用场景下都是没有问题的。然而,线程库的解决方案也有其先天缺陷。第一,如果没有在编程语言中定义内存模型的话,我们就不能清楚的定义到底什么样的编译器/CPU优化是合法的,而程序员也不能确定程序到底会怎么样被优化执行。例如,Pthreads标准中并未对什么是数据竞争做出精确定义,因此,C++编译器可能会进行一些错误优化从而导致数据竞争。第二,绝大多数情况下线程库能正确的完成任务,而在极少数对性能有更高要求的情况下(尤其是需要利用底层的硬件特性来实现高性能Lock Free算法时)需要更精确的内存模型以规定好程序的行为。简而言之,把内存模型集成到编程语言中去是比线程库更好的选择。

C++11中的内存屏障(内存顺序)

“松散”内存屏障

std::memory_order_relaxed,松散操作,没有同步或顺序制约,仅对此操作要求原子性。带标签 memory_order_relaxed的原子操作无同步操作;它们不会在并发的内存访问间强加顺序。它们只保证原子性和修改顺序一致性。

例如,对于最初为零的x和y:1

2

3

4

5

6

7r1 = y.load(memory_order_relaxed); // A

x.store(r1, memory_order_relaxed); // B

// 线程 2 :

r2 = x.load(memory_order_relaxed); // C

y.store(42, memory_order_relaxed); // D

允许产生结果r1 == 42 && r2 == 42,因为即使线程1中A先序于B且线程2中C先序于D ,却没有制约避免y的修改顺序中D先出现于A ,而x的修改顺序中B先出现于C。D在y上的副效应,可能可见于线程1中的加载A ,同时B在x上的副效应,可能可见于线程2中的加载C。

如上图所示,假设:a, b, c分别为普通变量,而x为atomic类型的变量,当在写入x时,设置memory_order_relaxed时,写入a,写入b的顺序,在另外的线程上看到的,完全可能是相互调换的,写入c的位置,完全也有可能由出现在写入x之前,而在另外一个线程上看到的,确实在写入x之后,同时,读出b的动作,完全有可能从写入x之后,编程在另外一个线程上看,是在写入x之前。也就是,各种乱序, 都是被允许的。

松散内存顺序的典型使用是计数器自增,只要求原子性,但不要求顺序或同步

单向的”释放”内存屏障

std::memory_order_release,释放操作,当前线程中的读或写不能被重排到此存储后。当前线程的所有写入,可见于加载该同一原子变量的其他线程。

如上图所示,在使用memory_order_release设置的原子操作写入的情况下,原子操作之后的读写操作,在另外的线程(加载了该原子变量的线程)看来,可以重排到原子操作之前(通俗点说,就是另外线程可能认为当前线程是先执行的写入的a操作,再执行的写入x操作)。但是,如下图所示,原子操作之前的读、写操作,计算机系统必须保证,在另外一个线程看来,他是在原子操作写入之前就被写入了, 不能是在原子操作之后才写入(通俗点说,也就是另外一个线程,不能认为当前线程先写入的x,再写入的c,他一定要看到的是先写入c,再写入的x):

单向的”加载”内存屏障

std::memory_order_acquire,加载操作,当前线程中读或写不能被重排到此加载前。其他释放同一原子变量的线程的所有写入,为当前线程所可见。

如上图所示,在设置memory_order_acquire的对x的读操作前的读、写操作,在另外的线程看来(或者说实际的运行顺序),可以被重排到对x的读操作后,(通俗点说,就是其他线程可以是认为当前线程先读取的x,再写入的c)。然而如下图所示,对x的读操作后的读、写操作,在另外的线程看来不允许被重排到x的读操作前(不允许其他线程看到当前线程先写入的a,再读取的x)。

单向“加载”+单向“释放”协议

往往memory_order_acquire和memory_order_release是配合着一起使用的:线程1使用memory_order_release写入原子变量x

线程2使用memory_order_acquire读出原子变量x

所有在线程1上,在写入x之前的写入操作,都将在线程2上,在读出x之后,被看到。使用单向“加载”+单向“释放”协议的场景往往是:线程1,写入一些实际数据,接着通过将原子变量x设置为某个值A(通过使用memory_order_release写入原子变量x)来“发布”这些数据。

线程2,通过读取并判断x已被设置为A(通过使用memory_order_acquire来读取原子变量x),进而读取线程1实际“发布”的那些数据

如上图所示,thread_1在release写入x(值:A)之前,写入了待发布的a,b的数据,而thread_2,将在acquire读出x且为A之后,将读到thread_1发布的a,b的数据。同时,我们可以注意到,在thread_2上,在acquire读出x之前,如果对a进行读操作,我们是无法确认读到的a一定会thread_1在之前最后写入的a,这里的顺序是不会被保证的,重排是被允许的。同时,在之后,读取c,读到的是否为thread_1最后写入的c,也是不确定的,因为,在x写入之后,thread_1上又出现了一次写入,而如果在此之前,还有一次写入, 这两次写入之间,是不存在限制,可能会被重排的。

thread_1上有了release_store,对于a,b的写入就一定会在x的改变之前,在thread_2上,就不会出现类似读出c,的不确定性。thread_2上有了acquire_load,右侧的读出a,就不会被重排读到左侧,而左侧读出a的不确定性,也不存在。thread_1卡住的是:对于数据a,b的写入不能排到x的写入之后,thread_2卡住的,是对于数据a,b的读取,不能排到读取x之前,这样,就保证了数据a,b,与”信号量”x,之间,在thread_1, thread_2上的同步关系。

双向的”加载-释放”内存屏障

std::memory_order_acq_rel,加载-释放操作,带此内存顺序的读-修改-写操作既是获得加载又是释放操作。没有操作能够从此操作之后被重排到此操作之前,也没有操作能够从此操作之前被重排到此操作之后,当然,具有这一限制的前提是,观察读写顺序的不同线程是使用的同一个原子变量并基于这一内存顺序。

最严的”顺序一致”内存屏障

std::memory_order_seq_cst,顺序一致操作,此内存顺序的操作既是加载操作又是释放操作,没有操作能够从此操作之后被重排到此操作之前,也没有操作能够从此操作之前被重排到此操作之后。带标签memory_order_seq_cst的原子操作不仅以与释放/加载顺序相同的方式排序内存(在一个线程中先发生于存储的任何结果都变成做加载的线程中的可见),还对所有拥有此标签的内存操作建立一个单独全序。memory_order_seq_cst比memory_order_acq_rel更强,memory_order_acq_rel的顺序保障,是要基于同一个原子变量的,也就是说,在这个原子变量之前的读写,不能重排到这个原子变量之后,同时这个原子变量之后的读写,也不能重排到这个原子变量之前。但是,如果两个线程基于memory_order_acq_rel使用了两个不同的原子变量x1, x2,那在x1之前的读写,重排到x2之后,是完全可能的,在x1之后的读写,重排到x2之前,也是被允许的。然而,如果两个原子变量x1,x2,是基于memory_order_seq_cst在操作,那么即使是x1之前的读写,也不能被重排到x2之后,x1之后的读写,也不能重排到x2之前,也就说,如果都用memory_order_seq_cst,那么程序代码顺序(Program Order)就将会是你在多个线程上都实际观察到的顺序(Observed Order)

atomic 内存序_C++内存屏障(内存顺序)总结相关推荐

  1. java 内存指针_C指针和内存

    我正在学习C,现在我撞墙了 . 我很难理解指针 . 想象一下,我有这个代码: #include #include #include #define DELTA 33 int calls, seed=3 ...

  2. html5 自带video内存泄露_C++ 如何避免内存泄露?

    来源:知乎-张凯(Kyle Zhang) [CPP开发者导读]:内存泄漏是C/C++的一个老生常谈的问题,无论是新手还是有经验的开发者都会在这个问题上栽跟头. 本文向读者介绍了如何避免内存泄漏的方法和 ...

  3. c linux new使内存耗尽_C/C++的内存泄漏检测工具Valgrind memcheck的使用经历

    Linux下的Valgrind真是利器啊(不知道Valgrind的请自觉查看参考文献(1)(2)),帮我找出了不少C++中的内存管理错误,前一阵子还在纠结为什么VS 2013下运行良好的程序到了Lin ...

  4. c# 定位内存快速增长_c#如何避免内存分配瓶颈以提高多线程性能

    我使用C#作为研究工具,经常需要运行CPU密集型任务,例如优化.从理论上讲,我应该能够通过多线程化代码来提高性能,但实际上当我使用与工作站上可用内核数量相同的线程数时,我通常会发现CPU仍然只运行在2 ...

  5. C语言与JAVA内存管理_C语言动态内存管理和动态内存分配

    动态内存管理同时还具有一个优点:当程序在具有更多内存的系统上需要处理更多数据时,不需要重写程序.标准库提供以下四个函数用于动态内存管理: (1) malloc().calloc() 分配新的内存区域. ...

  6. 聊聊内存模型和内存序

    本文始发于公众号[高性能架构探索],本公众号致力于分享干货.硬货以及工作上的bug分析,欢迎关注.回复[pdf]免费获取计算机经典书籍 你好,我是雨乐! 最近群里聊到了Memory Order相关知识 ...

  7. C++11多线程 内存序(std::memory_order_consume)

    引言 在C++多线程 内存序系列文章中,已经介绍了如下三种内存序 C++11多线程 内存序(std::memory_order_seq_cst ) C++11多线程 内存序(std::memory_o ...

  8. 浅谈内存屏障,C++内存序与内存模型

    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可. 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权. 文章目录 引言 一个有意思的问题 ...

  9. c# 定位内存快速增长_C#和halcon的混编程序出现序内存泄露、句柄持续增加、视觉程序运行越来越慢等问题的处理...

    这个话题非常重要.勇哥苦于手中的项目遇到这样的问题. 这些问题表现是: (1)视觉程序内存占用会越来越大.这种内存变大通常是缓慢变大,几天不关机持续运行的话,大到几个G也不稀罕.通常,急速内存变大要容 ...

最新文章

  1. android-support-v4.jar 免积分下载
  2. 当 position:sticky 遇到 bootstrap 浮动布局时候的踩坑记录
  3. 在Ubuntu上安装openResty #1
  4. 【C++】Visual Studio教程(八) -修复 Visual Studio
  5. 欢乐纪中A组赛【2019.8.10】
  6. 如何ping通服务器的公网IP?
  7. 数据结构链表例程_如何掌握RxJava例程的四个结构
  8. 热烈祝贺人生第一篇论文发表成功
  9. 「成人学习」掘金者,从教育走向生活方式
  10. 关闭windows端口的批处理命令
  11. css+html投票系统,网上在线投票系统的设计与实现.doc
  12. python实现明星专家系统:人脸识别自动比对
  13. SEO入门一篇就够-SEO教程
  14. 共享充电,是雪中送炭还是暗藏危险?——恶意充电宝实验
  15. Tesla P40在Windows10专业版下走核显输出
  16. 西门子博途软件TIA PORTAL不同版本安装在一台电脑上的个人总结
  17. 一文带你读完《推荐系统实践》
  18. MathType 数学公式编辑器[Baidu]
  19. guidata handles理解
  20. 乐2Pro_乐视X625_官方线刷包_救砖包_解账户锁

热门文章

  1. 隐私求交| Simple, Fast Malicious Multiparty Private Set Intersection
  2. 2020最新互联网数据调查显示,Kotlin-势必取代-Java?,java从入门到精通下载
  3. win10专业版180天bat激活脚本
  4. 扫地机器人测评云鲸_小白鲸成清洁电器中“明星”,开箱测评云鲸扫地机器人好不好?...
  5. Java线程泄露的分析与处理
  6. 基于单片机的数字温度计设计-零妖
  7. 有关研究生如何进行科研的研究
  8. 装系统那些事儿-1-电脑的启动流程
  9. vb远程访问dde服务器,VB 利用DDE进程间通信,5行代码搞定
  10. HTML5怎么创建第一个步骤,创建网站的一般步骤是什么,流程有哪些?