C++ 并行编程之原子操作的内存顺序

C++ 并行编程之原子操作的内存顺序

1. C++原子操作的内存顺序概述

  1. memory order主要有以下几种:
  • memory_order_relaxed
    只提供对单个atomic变量的原子读/写,不和前后语句有任何memory order的约束关系。

  • memory_order_consume
    程序可以说明哪些变量有依赖关系,从而只需要同步这些变量的内存。
    类似于memory_order_acquire,但是只对有依赖关系的内存。意思是别的CPU执行了memory_order_release操作,而其他依赖于这个atomic变量的内存会被执行memory_order_consume的CPU看到。这个操作是C++特有的,x86也不支持这种类型的memory order,不清楚其他种类的cpu是否支持,这还涉及到编译器是否支持这种细粒度控制,也许它直接按memory_order_acquire来处理。

  • memory_order_acquire
    执行memory_order_acquire的cpu,可以看到别的cpu执行memory_order_release为止的语句对内存的修改。执行memory_order_acquire这条指令犹如一道栅栏,这条指令没执行完之前,后续的访问内存的指令都不能执行,这包括读和写。

  • memory_order_release
    执行memory_order_release的cpu,在这条指令执行前的对内存的读写指令都执行完毕,这条语句之后的对内存的修改指令不能超越这条指令优先执行。这也象一道栅栏。
    在这之后,别的cpu执行memory_order_acquire,都可以看到这个cpu所做的memory修改。

  • memory_order_acq_rel
    是memory_order_acquire和memory_order_release的合并,这条语句前后的语句都不能被reorder。

  • memory_order_seq_cst
    这是比memory_order_acq_rel更加严格的顺序保证,memory_order_seq_cst执行完毕后,所有其cpu都是确保可以看到之前修改的最新数据的。如果前面的几个memory order模式允许有缓冲存在的话,memory_order_seq_cst指令执行后则保证真正写入内存。一个普通的读就可以看到由memory_order_seq_cst修改的数据,而memory_order_acquire则需要由memory_order_release配合才能看到,否则什么时候一个普通的load能看到memory_order_release修改的数据是不保证的。

  1. x86的memory order
    x86的memory order是一种strong memory order,它保证:
  • LoadLoad是顺序的
    一个cpu上前后两条load指令是顺序执行的,前面一条没执行完毕,后面一条不能执行
  • StoreStore是顺序的
    一个cpu上前后两条store指令是顺序执行的,前面一条没执行完毕,后面一条不能执行
  • LoadStore
    一个cpu上前面一条是Load指令,这条指令没执行完毕,后面一条store不能执行

2. 为什么需要定义这样内存顺序

简单的解释,就是cache的存在,虽然前面更新了变量的值,但是可能存在某个CPU的缓存中,而其他CPU的缓存还是原来的值,这样就可能产生错误,定义内存顺序就可以强制性的约束一些值的更新。
为什么需要这么多类型?灵活度更大,合理的使用可以最大化利用系统的性能。

3. 内存操作的种类

  • Store operations —— 写
    which can have memory_order_relaxed , memory_order_release ,or memory_order_seq_cst ordering
  • Load operations —— 读
    which can have memory_order_relaxed , memory_order_consume ,memory_order_acquire, or memory_order_seq_cst ordering
  • Read-modify-write operations —— 读-修改-写
    which can have memory_order_relaxed , memory_order_consume , memory_order_acquire , memory_order_release , memory_order_acq_rel, or memory_order_seq_cst ordering

4. 内存模型关系

为什么要理解这个,其实这就是定义内存顺序的目的,因为有了这些关系的需求,才要定义这些顺序。

  • happens-before
    对同一线程,就是一个操作要在另一个操作之前执行。对多个线程,某一个线程的操作A要在另一线程的操作B之前发生。若顺序变了,可能就不能达到我们希望的效果。

  • synchronizes-with
    某一线程的load操作,依赖于另一线程的store操作,简单说,就是线程A读取的变量值a,应该是线程B修改后的值。

5. C++定义的内存顺序分别可以实现哪些关系?

  • relaxed 松散顺序
    memory_order_relaxed 由这个值指定的,这个很简单,就是各个CPU读取的值是未定义的,一个CPU在一个线程中修改一个值后,其他CPU不知道。

  • sequentially-consistent 顺序一致顺序
    memory_order_seq_cst 由这个值指定,这个也很简单,相当于各CPU的原子操作都是在一个线程上工作,一个修改后,其他CPU都会更新到新的值。
    如下例,在此顺序下,只要x,y的值修改了,其他线程的load操作就会是最新的值,所以线程c和d,不管哪个先执行完,后执行完的肯定会修改z,所以一定不会报异常。但是要是松散模型,即使x,y变了,但是在线程cd load时,也可以是之前为未修改的值。

#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x() //a
{
x.store(true,std::memory_order_seq_cst);
}
void write_y() //b
{
y.store(true,std::memory_order_seq_cst);
}
void read_x_then_y() //c
{
while(!x.load(std::memory_order_seq_cst)); //  1
if(y.load(std::memory_order_seq_cst))  // 2
++z;
}
void read_y_then_x() //d
{
while(!y.load(std::memory_order_seq_cst)); // 3
if(x.load(std::memory_order_seq_cst)) // 4
++z;
}
int main()
{
x=false;
y=false;
z=0;
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join();
b.join();
c.join();
d.join();
assert(z.load()!=0);
}
  • acquire-release 获得-释放顺序
    acquire操作(load)—— memory_order_consume , memory_order_acquire
    release操作(store)—— memory_order_release
    acquire-release操作 —— memory_order_acq_rel (先acquire再做一次release,对于fetch_add这样的操作,既要load一下,又要store一下的,就用这个)
    一个原子做了这个acquire操作,肯定是读的是这个原子最后一次release操作修改的值。换句话说,要是采用release的方式store一个值,那么其他CPU都会看到这一次的修改。
    (不举例子了,C++并发编程实战这本书上有,理解后再去看还是比较简单的)

  • memory_order_consume 和 memory_order_acquire 有什么区别?
    memory_order_consume: 只能保证当前的变量,不能保证此线程中此load操作语句之前的其他变量的操作也被其他CPU看到,也就是只能保证自己在其他CPU上都更新了。(这样说不准确,应该做可以保证当前变量,以及此线程中此语句之前的,并且与当前变量有依赖关系的变量的修改被其他CPU看到)
    memory_order_acquire:不仅能保证当前变量的修改被其他CPU看到,也能保证这条语句之前的其他变量的load操作的值,也被其他CPU看到,不论其他变量是什么顺序的(relax的也会更新)。

参考

[1] 知乎上的这个回答,写的很好: 如何理解 C++11 的六种 memory order? - 知乎
[2] C++11 memory order-博客-云栖社区-阿里云
[3] 官方文档写的还可以(当我理解了,才觉得很好) std::memory_order - cppreference.com
[4] 《C++并发编程实战》或者《C++ Concurrency in Action》

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值