C++Atomic与内存序

本文深入探讨了C++11及后续版本的内存模型,介绍了happens-before原则,store和load操作,以及内存序的不同概念,如memory_order_seq_cst、acquire、release等,同时提供了原子操作在多线程同步中的应用场景实例。
摘要由CSDN通过智能技术生成

这篇文章简述C++11之后的内存模型和Atomic中使用的内存序(memory order)。个人作品,禁止转载

参考文献

Memory Models for C/C++ Programmers

相关概念

happens-before

如果语句A和语句B在代码中依次出现,且B的运行依赖A产生的结果,则A必须在B之前执行且B能够看到A的结果,即A happens before B

// example
auto A = 3;
auto B = A * A + 123;
auto C = 10;

则程序的正确运行必须保证A happens before B,但是不需要保证A/B happens before C。

store & load

store即向内存写入,load即从内存读取

synchronized-with

举例说明:考虑2个线程

// thread A
prepare_data();
ready_flag = true;	// <---------------------- point A1
do_other_things();
// thread B
while(ready_flag == false);	// <----------------------- point B1
process_data();

A线程按顺序执行完point A1语句之后,B线程开始执行point B1之后的语句。这种关系是一种同步关系,即B synchronized-with A。这只是一个例子,并非synchronized-with的准确定义

内存模型与内存序

重排

再看这个例子

// thread A
prepare_data();
ready_flag = true;	// <---------------------- point A1
do_other_things();

现代计算机的CPU比内存访问快太多,因此出于性能考虑,要将部分指令重排,以减少内存访问次数。比如编译器或者CPU可能将如上片段重排为

ready_flag = true;	// <---------------------- point A1
prepare_data();
do_other_things();

这使得程序执行错误。因为编译器并不知道ready_flag = true;这句话不能向上重排。因此需要对指令重排进行限制,具体体现为对内存序。

Atomic

C++的原子类型,定义了如下方法

load(std::memory_order order) 
store(T desired, std::memory_order order)
exchange(T desired, std::memory_order order)
compare_exchange_weak(T& expected, T desired, std::memory_order order)
compare_exchange_strong(T& expected, T desired, std::memory_order order)

对整数类型和浮点类型,定义了自加和自减

fetch_add(T arg, std::memory_order order)
fetch_sub(T arg, std::memory_order order)

对整数类型,还定义了原子位运算

fetch_and(T arg, std::memory_order order)
fetch_or(T arg, std::memory_order order)
fetch_xor(T arg, std::memory_order order)

默认情况下,memory_order 为 memory_order_seq_cst,即顺序一致性。比如

atomic<int> i32;
i32++;	// memory_order_seq_cst
i32.fetch_add(2); // memory_order_seq_cst

内存序

C++11定义了如下内存序

memory_order_seq_cst
memory_order_acq_rel
memory_order_release
memory_order_acquire
memory_order_consume
memory_order_relaxed

其中

  • memory_order_acquire,memory_order_consume只能用于读操作
  • memory_order_release只能用于写操作
  • memory_order_acq_rel只能用于读-改-写操作
  • memory_order_relaxed即不需要限制内存序,只需要保证原子性
  • memory_order_seq_cst顺序一致性,即不允许任何重排,默认
    解释如下(如下解释中,load和store都是针对同一个atomic变量)
内存序解释
memory_order_acquire当前load操作之后的所有读、写操作都不能被重排到该load操作上方
memory_order_comsume当前load操作之后的,所有依赖于当前所load的变量的读、写操作都不能被重排到该load操作上方
memory_order_acq_rel本线程中,所有该操作上方的读写操作都不能被重排到该操作下方,该操作下方的读写操作也不能被重排到该操作上方。其它线程中,用release进行的store操作之前的所有写操作,都在该读写改操作之前可见
memory_order_release本线程中该操作之前的所有读写操作都不能被重排到该操作下方
memory_order_seq_cst可以用于store和load,也可以用于读写改。该操作之前的所有读写,不允许被重排到该操作下方;该操作之后的所有读写,不允许被重排到该操作上方

原子量线程同步的例子

如下三种内存序组合,可以用于线程同步

  • memory_order_seq_cst and memory_order_seq_cst
  • memory_order_release and memory_order_acquire
  • memory_order_release and memory_order_consume
memory_order_seq_cst and memory_order_seq_cst
// thread A
c = 0;
x = 0;
a = 1;
b = another_variable;
x.store(1, std::memory_order_seq_cst);     // <----------------Point A
c = 2;
// thread B
cout << c << endl;
while(x.load(std::memory_order_seq_cst) == 1);	// <----------------Point A
cout << a << endl;

线程B的输出,只有可能是0,1。在Point A之前,A线程中a必然已经被赋值,c必然是0。Point A之前的读写操作不会跑到Point A之后,反之亦然

memory_order_release and memory_order_acquire
// thread A
c = 0;
......
b = 1;
x.store(1, std::memory_order_release);     // <----------------Point A
c = 2;
// thread B
while(x.load(std::memory_order_acquire) == 1);    // <----------------Point A
cout << b << c << endl;

线程B的输出,变量b一定是1,因为x.store(1, std::memory_order_release); 之前的读写操作都不能被重排到它下方,线程B的load操作之后的所有读写操作,都不会被重排到它上方。c可能是0或者2,因为memory_order_release并不限制store操作之后的读写操作的重排。

memory_order_release and memory_order_consume
// thread A
c = 0; b = 0;
......
b = 1;
x.store(1, std::memory_order_release);     // <----------------Point A
c = 2;
// thread B
while(x.load(std::memory_order_consume) == 1);    // <----------------Point A
cout << b << c << endl;
cout << x << endl;

线程B的输出,b可能是1和0,因为b可能在x.load之前就读取了;c可能是0或者2,因为它并没有被原子量限制读取顺序,x必然是1,因为x的值和x.load有关,它不能被重排到x.load上方

实践中如何使用

  • 如果没搞清楚,就用默认的memory_order_seq_cst
  • 如果只需要原子性,不需要线程间同步,就用memory_order_relaxed。比如需要多线程共享一个计数器,但是并不需要该计数器绝对准确。

相关概念:内存屏障

之后的C++版本还引入了内存屏障,和内存序相似

  • acquire_memory_fence( void )ensures that all subsequent operations in program order are performed after all preceding loads in program order;
  • release_memory_fence( void )ensures that all preceding operations in program order are performed before all subsequent stores in program order;
  • acq_rel_memory_fence( void ), combines the semantics of acquire and release;
  • ordered_memory_fence( void ), ensures that all preceding operations in program order are performed before all subsequent operations in program order.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值