前置铺垫

指令重排

指令重排是指编译器或CPU为了优化程序的执行性能而对指令进行重新排序的一种手段。

在非并发情景下他并不会改变最终执行结果,但会更改执行过程顺序。

但在并发情景下,指令重排会引发意想不到的错误。

我们在编程时,编写的代码是这样的。

int main() {
int a = 1;
int b = a;
int c = 2;
int d = c;
return 0;
}

但编译后则可能会出现以下顺序

int main() {
int a = 1;
int c = 2;
int b = a;
int d = c;
}

重排前后的最终结果a = b = 1,c = d = 2

指令重排遵循顺序执行原则,保证单线程内语义的串行性,即不管怎么进行指令重排序,单线程内程序的执行结果不能被改变。

内存屏障

在并发编程中,我们希望在部分代码段禁用指令重排。

加入内存屏障的代码段将禁用指令重排。

更为严格的描述:重排后的指令不可能插入至有内存屏障的代码段。

内存顺序

位于<atomic>标头内。

在C++中封装了以下内存顺序。

enum class memory_order : int
{
relaxed,
consume,
acquire,
release,
acq_rel,
seq_cst
};

inline constexpr memory_order memory_order_relaxed = memory_order::relaxed;
inline constexpr memory_order memory_order_consume = memory_order::consume;
inline constexpr memory_order memory_order_acquire = memory_order::acquire;
inline constexpr memory_order memory_order_release = memory_order::release;
inline constexpr memory_order memory_order_acq_rel = memory_order::acq_rel;
inline constexpr memory_order memory_order_seq_cst = memory_order::seq_cst;

原子操作默认使用std::memory_order::seq_cst

std::memory_order::relaxed

  • 只保证原子操作,不保证指令顺序。
// no barrier
expression with std::memory_order::relaxed;
// no barrier

std::memory_order::consume

  • 有此内存序的加载操作,在其影响的内存位置进行消费操作。
  • 当前线程中依赖于当前加载的值的读或写不能被重排到此加载之前。其他线程中对有数据依赖的变量进行的释放同一原子变量的写入,能为当前线程所见。在大多数平台上,这只影响到编译器优化。

std::memory_order::acquire

  • 用于获取(load)原子变量值相关的操作。
  • 对于使用memory_order_acquire内存顺序的指令,该指令后面的所有读写操作不能重排在该指令之前。
  • 当前线程执行的memory_order_acquire指令能够保证读到其他线程memory_order_release指令之前的所有内存写入操作。
// barrier ^
expression with std::memory_order::acquire;

std::memory_order::release

  • 用于设置(store)原子变量值相关的操作。
  • 对于使用memory_order_release内存顺序的指令,该指令之前的所有读写操作不能重排在该指令之后。
  • 当前线程memory_order_release指令之前的所有内存写操作对于其他线程memory_order_acquire指令之后可见。
expression with std::memory_order::release;
// barrier v

std::memory_order::_seq_cst

// barrier ^
expression with std::memory_order::_seq_cst;
// barrier v

释放-获取定序(Acquire-Release)

无锁队列