• 第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 不能被重排。

    在了解相关内容时,发现这个回答最容易理解。
  • 相关阅读:
    CSS BEM 命名规范简介
    React 端的编程范式
    在React应用程序中用RegEx测试密码强度
    React 中获取数据的 3 种方法及它们的优缺点
    vue props传值常见问题
    如何理解vue中的v-model?
    利用jQuery not()方法选取除某个元素外的所有元素
    初识Nest.js
    react-绑定this并传参的三种方式
    Angular怎么防御xss攻击?
  • 原文地址:https://www.cnblogs.com/hebust-fengyu/p/12087824.html
Copyright © 2020-2023  润新知