Herb Sutter在一次对C++11内存模型的演讲中提到C++11的内存模型,让C++有了标准独立于编译器和平台线程库和标准的多线程内存控制方式。
咋一看这个句话很奇怪,难道C++98/03及以前的内存模型不支持多线程吗, 用C++03的标准不照样写多线程程序。 其实我忽略了一个事实在B家同学们都是gcc & linux,潜意识posix标准的pthread就是C++的线程库存,其实还有cl & windows:)
C++标准是基于一个抽象的机器制定的(大部分语言标准应该也是这样),它没有具体的CPU或编译器。 C++98/03标准没有对每次读写(loads and stores)以及执行顺序作出规定,所以无法写出完全可移植的多线程代码(能同时支持freebsd,linux和windows就很不错了)。C++11标准则在设计上引入了支持多线程的内存模型,它规定了在多线程环境中内存的读写的操作以及可能的执行顺序1。
在cppreference.com上可以看到load和store的原型如下,
T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept;
void store( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;
对于memory order多线程环境中(CPU也是乱序执行的)的几种规定如下下表,
Value | Explanation |
---|---|
memory_order_relaxed | 对其它读写操作没有同步,只保证本操作是原子的 |
memory_order_consume | load操作,当前线程依赖该原子变量的访存操作不能reorder到该指令之前,对其他线程store操作(release)可见 |
memory_order_acquire | load操作,当前线程所有访存操作不能reorder到该指令之前,对其他线程store操作(release)可见 |
memory_order_release | store操作,当前线程所有访存操作不能reorder到该指令之后,对其他线程load操作(consume)可见 |
memory_order_acq_rel | load/store操作,memory_order_acquire + memory_order_release |
memory_order_seq_cst | memory_order_acq_rel + 顺序一致性(sequential consisten) |
关于memory_order_seq_cst与memory_order_acq_rel,下面这段代码很直观的体现了,
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x()
{
x.store(true, std::memory_order_seq_cst);
}
void write_y()
{
y.store(true, std::memory_order_seq_cst);
}
void read_x_then_y()
{
while (!x.load(std::memory_order_seq_cst))
;
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
void read_y_then_x()
{
while (!y.load(std::memory_order_seq_cst))
;
if (x.load(std::memory_order_seq_cst)) {
++z;
}
}
int main()
{
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); // will never happen
}
<p>
知乎上有个G家的stephen w很认真的翻阅了经典计算机体系结构:量化研究方法给了不错的总结2,
"SC要求所有内存操作表现为(appear)逐个执行(任一次的执行结果都像是所有处理器的操作都以某种次序执行),每个处理器中的操作都以其程序指定的次序执行。SC有两点要求:在每个处理器内,维护每个处理器的程序次序;在所有处理器间,维护单一的表征所有操作的次序。对于写操作W1, W2, 不能出现从处理器 P1 看来,执行次序为 W1->W2; 从处理器 P2 看来,执行次序却为 W2->W1 这种情况。
<p>
参考链接