第5章 C++内存模型和原子类型操作

5.1 内存模型基础

5.1.1 对象和内存位置

C++程序中所有的数据均是由对象组成的,但是其中有一些不能够派生的类,这些数据在内存中有着严格的放置顺序,这样才保证了开发的正确性,在一个复合结构中我们可以得到:

1.每个变量都是一个对象,包括其他对象的成员

2.每个对象占据至少一个内存位置

3.如int或者char这样的基本类型的变量恰好一个内存位置,无论其大小,即使他们相邻或者是数组的一部分

4.相邻的位域是相同内存位置的一部分

//对于3,4两点,感觉怪怪的

5.1.2 对象、内存位置以及并发

对于并发访问对象,如果多个线程访问的对象没有同一位置的,则没必要考虑由数据竞争导致的写入、读取完整性的问题,只有出现数据竞争时才应该考虑数据的写入、读取之间的完整性。

5.1.3 修改顺序

C++程序中每个对象,都有一个确定的修改顺序,系统中的所有线程必须一致同意此顺序

5.2 C++中的原子操作及类型

原子操作是一个不可分割的操作,这种操作有个特点,要么做完,要么没做完,在其他线程访问的时候,不能够访问到这种过程的中间态。

5.2.1 标准原子类型

标准原子类型有很多

5.2.2 std::atomic_flag上的操作

对于std::atomic_flag的初始化只能使用ATOMIC_FLAG_INIT。

5.2.3 std::atomic<bool>的操作

5.2.4 std::atomic<T*>上的操作:指针算数运算符

5.2.5 标准原子整型的操作

5.2.6 std::atomic<>初级类模板

5.2.7 原子操作的自由函数

对于原子操作并非只有成员函数,当然也存在非成员函数,对于大多数非成员函数只是在原来函数基础上添加atomic_前缀。在有机会指定内存顺序标签的地方,他们有两个变种:一个是没有标签的,一个是添加_explict后缀和额外的参数作为内存顺序的标签。

 

原子类型的可用操作
操作 atomic_flag atomic<bool> atomic<T*> atomic<integral-type> atomic<othre-type>
test_and_set        
clear        
is_lock_free  
load  
store  
exchange  
compare_exchange_weak  
compare_exchange_strong          
fetch_add, +=      
fetch_sub, -=      
fetch_or, |=        
fetch_and, &=        
fetch_xor, ^=        
++, --      

5.3 同步操作和强制顺序

5.3.1 synchronizes-with 关系

5.3.2 happens-before 关系

5.3.3 原子操作的内存顺序

借鉴:知乎如何理解C++11的六种memory_ordered的第一个回答:https://www.zhihu.com/question/24301047/answer/85844428

1. releaxed ordering: 在单线程内,所有原子操作是顺序进行的,按照什么顺序?基本上就是代码顺序(sequenced-before)。这就是唯一的限制了!两个来自不同线程的原子操作是什么顺序?两个字:任意。

2. Release -- acquire: 来自不同线程的两个原子操作顺序不一定?那怎么能限制一下它们的顺序?这就需要两个线程进行一下同步(synchronize-with)。同步什么呢?同步对一个变量的读写操作。线程 A 原子性地把值写入 x (release), 然后线程 B 原子性地读取 x 的值(acquire). 这样线程 B 保证读取到 x 的最新值。注意 release -- acquire 有个牛逼的副作用:线程 A 中所有发生在 release x 之前的写操作,对在线程 B acquire x 之后的任何读操作都可见!本来 A, B 间读写操作顺序不定。这么一同步,在 x 这个点前后, A, B 线程之间有了个顺序关系,称作 inter-thread happens-before.

3. Release -- consume: 我去,我只想同步一个 x 的读写操作,结果把 release 之前的写操作都顺带同步了?如果我想避免这个额外开销怎么办?用 release -- consume 呗。同步还是一样的同步,这回副作用弱了点:在线程 B acquire x 之后的读操作中,有一些是依赖于 x 的值的读操作。管这些依赖于 x 的读操作叫 赖B读. 同理在线程 A 里面, release x 也有一些它所依赖的其他写操作,这些写操作自然发生在 release x 之前了。管这些写操作叫 赖A写. 现在这个副作用就是,只有 赖B读 能看见 赖A写. (卧槽真累)
4. Sequential consistency: 理解了前面的几个,顺序一致性就最好理解了。Release -- acquire 就同步一个 x,顺序一致就是对所有的变量的所有原子操作都同步。这么一来,我擦,所有的原子操作就跟由一个线程顺序执行似的。

aquire语义:load 之后的读写操作无法被重排至 load 之前。即 load-load, load-store 不能被重排。

release语义:store 之前的读写操作无法被重排至 store 之后。即 load-store, store-store 不能被重排。

在了解相关内容时,发现这个回答最容易理解。
posted @ 2019-12-28 17:04  楓羽  阅读(569)  评论(0编辑  收藏  举报