博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++多线程内存模型
阅读量:2428 次
发布时间:2019-05-10

本文共 2820 字,大约阅读时间需要 9 分钟。

文中图片摘自《c++ concurrency in action》用于自我学习,存在不足,还请指正。

内存模型基础

在C/C++中,无论是常规的整型变量,还是结构体或是C++中的类,在计算机中都是在一定的内存位置进行存储的。而这个内存位置是在初始化就已经确定好,不同的内存位置,那么程序访问变量的顺序也有就不同。同样,在多线程程序中,如果程序的执行顺序不同,那么结果就有所区别。因此多线程程序中,原子操作是更接近于底层的操作,数据的存储顺序需要指定,以明确程序的运行顺序,达到避免数据竞争的作用。

常见模型介绍

在标准库<atomic>中,原子操作也提供了相关的参数进行设置数据存储顺序。原子操作可以分为三类操作,其可选择的顺序如下:

1.Store操作:memory_order_relaxed,memory_order_release,memory_order_seq_cst;
2.Load操作:memory_order_relaxed,memory_order_consume,memory_order_acquire,memory_order_seq_cst;
3.Read-modify-write(读-改-写)操作:memory_order_relaxed,memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel,memory_order_seq_cst。
所有操作的默认顺序都是memory_order_seq_cst。
虽然有6个选择,但是只表示3种内存模型
1.排序一致序列(sequentially consistent)
2.获取释放序列(memory_order_consume、memory_order_acquire、memory_order_release、
memory_order_acq_rel)
3.自由序列(memory_oder_relaxed)

下面简单介绍三种内存模型

1.排序一致序列

程序中的行为从任意角度去看,序列顺序都保持一致。如果原子类型实例上的所有操作都是序列一致的,那么一个多线程程序的行为,就以某种特殊的排序执行,好像单线程那样,是可以预测执行顺序的。这是最容易理解的内存序列,所以设置为默认方式,但是简单存在一定性能上的代价。

如:对于多处理机计算机,因为整个序列的操作都需要在多个处理机上保持一致,这就需要对处理器间同步操作进行扩展,其代价是很大的,这降低了处理机的性能。

在这里插入图片描述

在这里插入图片描述
如上程序,assert()永远不会触发,同一变量的操作还是服从先发执行的关系,也就是存储store()操作发生在load()之前;在顺序一致序列中,各个线程按照一定顺序进行,线程之间自动进行了同步。例如:如果一个线程看到了x.load()为true,y.load()为false,那么可以推断出x.store()线程一定发生在y.store()线程之前,这就是这几个线程进行了同步,线程能够互相看到其他线程及时更新的数据。
如下图的实线表示的执行顺序
在这里插入图片描述
非排序一致序列内存模型,以memory_order_relaxed为例,并不能保证两个线程进行了同步。

2.自由序列

以自由序列进行原子操作时,线程之间没有任何的同步关系。通过例子进行讲述自由序列的顺序问题。

在这里插入图片描述
程序assert()可能会触发。在线程a中,对x和y进行了store()操作,但是线程b与a没有同步关系,所以在执行线程b时,执行y.load()操作可能会获得false,一直进行循环等待,并再次进行访问y.load();可能会获得线程a中对y.store()的更新得到true,然后跳出循环,执行x.load(),但是x与y并没有相关性,所以同样的可能会得到false的结果,就会触发assert()。这运行的结果充满了未知性、不确定性。
其中一种可能的执行顺序

在这里插入图片描述

也就是说memory_order_relaxed内存模型线程间是不存在任何同步关系。在多线程中,没有进行数据同步,会出现与设想的输出不一致,尽量不要单独使用这种模型,如果使用一定要添加其他原子操作或是内存删栏来保证数据的同步。

3.获取-释放序列

这个序列是自由序列的加强版;虽然没有统一的顺序,但是该模型引入了同步

其中原子加载就是获取操作(memory_order_acquire),原子存储就是释放操作(memory_order_release),原子读-改-写操作(如fetch_add()或是exchange())在这里,不是获取,就是释放,或是两者皆有的操作(memory_order_acq_rel)。内存模型中,释放操作和获取操作同步

程序见排序一致序列

程序的assert()可能会触发(如自由排序那样),因为x和y是由两个不同进程写入,所以序列中线程的每一次释放到获取都不会影响到其他的线程操作。

下图为执行的过程。
在这里插入图片描述
图中可以看出,如前面自由序列一样,线程之间看到的数据可能出现完全不同。因为特定的执行顺序,而且两线程之间也没有进行同步。
将程序进行更改为如下:
在这里插入图片描述
上述程序不会触发assert()。write_x()与write_y()合并为一个函数,对x和y在同一个进程中写入,在线程b中读取y.load()时,一直自旋等到y.load()返回true,那么读取x.load()时,因为先行关系(x.store()在y.store()之前),所以x.store()已经将true保存,而且x.store()与y.store()是同一个线程,也就是同一内存,若线程b能看到y.load()是true,跳出循环,那么必然能看到x.load()是true;这种情况不会触发assert()。加载与释放操作必须成对,无论有何影响,释放操作存储的值,必须要让获取操作看到,也就是加载和释放操作进行同步传递数据。

特别的:

在原子操作载入指向数据的指针时,可以使用memory_order_consume内存顺序作为加载语义,memory_order_release作为存储语义,可以进行同步,因为memory_order_consume是完全数据相关的,可以携带数据;

在这里插入图片描述
如上述程序,assert()的#4和#5不会触发,因为p.load标记为memory_order_consume,这就表示只有p.store()操作先行于p.load()操作,前面的那些a.store()操作不保证先行;而且使用memory_order_consume标记,也表示x变量对加载p操作有数据相关性,即能通过此获得x变量的数据,所以不会触发。但是a.store()、a.load()使用的自由序列,且与p没有数据相关性,所以assert()的#6会触发

转载地址:http://hsjmb.baihongyu.com/

你可能感兴趣的文章
八大 IoT 安全关键技术解析
查看>>
有钱 Python,没钱 PHP,编程语言也嫌贫爱富
查看>>
Docker是啥?容器变革的火花?
查看>>
假如从餐饮店的角度来看架构…
查看>>
这个充电宝太黑科技了,又小又不用自己带线,长见识了~
查看>>
HDC.2019后再发力,AppGallery Connect服务新升级
查看>>
网易云音乐热评的规律,44万条数据告诉你
查看>>
超神!GitHub 标星 5.5w,如何用 Python 实现所有算法?
查看>>
扛住100亿次请求——如何做一个“有把握”的春晚红包系统
查看>>
在北京看场雪为什么这么难?
查看>>
新年了,5G手机芯片,到底买谁?
查看>>
疫情之下「在家办公模式」开启,你该选择哪些远程协同工具?
查看>>
如何使用pdpipe与Pandas构建管道?
查看>>
远程办公的33种预测
查看>>
阿里巴巴架构师:十问业务中台和我的答案
查看>>
华为云发布三类六款计算实例 打造更强云端计算能力
查看>>
PHP 语言地位遭受挑战,PHP 程序员路在何方?
查看>>
PostgreSQL好评如潮,它是如何做到的?
查看>>
2017码云群英会,共享开源技术盛宴
查看>>
看完这份参会指南,Get 2017 OSC 年终盛典正确参会姿势!
查看>>