C++11中的内存模型

C++11中引入的内存模型。

内存模型含义

C++内存模型有两种含义:1、对象在内存中的布局;2、多线程/进程访问同一内存时的先后顺序以及同步。

对象在内存中的布局

任何一个对象都会占用内存,这些内存有可以分为:堆、栈、代码段、全局存储区/静态存储区、常量存储区。
对象有可以分为基本类型和复合类型。复合类型有可以分为POD类型和非POD类型。这一块内容可以参考《深入探索C++对象模型》。

多线程访问内存中的对象

C++11引入了内存模型概念。内存模型对atomic类型对象的访问做了限制,尤其是多线程环境下的限制。使用方法通常是在多线程环境下,对共享的atomic对象加上memory order,这样线程读写这个atomic对象及其上下文中的变量就有了限制。
在一个线程中,对私有变量的操作通常会被编译器优化,例如reorder;即使编译器做了优化,但是不会影响这个线程执行语义,这是编译器优化的前提。但是在多线程环境下,编译器的优化以及其他原因(例如CPU Cache),一个线程对变量操作不一定对另一线程可见。这里有2层含义:1、线程对变量进行了操作,但是对另一线程不可见。2线程对多个变量进行操作,但是另一线程看到这些操作的顺序和实际执行顺序不一致。
在具体了解内存模型前xi你看两个关系happen-before和synchronizes-With

happen-before

happend-before定义:Let A and B represent operations performed by a multithreaded process. If A happens-before B, then the memory effects of A effectively become visible to the thread performing B before B is performed.

简单来说,多线程环境下两个操作A、B,如果A操作先执行,那么B操作执行时可以看到A操作的执行效果。先让happen-before是有传递关系的。

实际中,我们按照预想的happen-before关系来写代码,但不一定可以得到happen-before的效果。例如1、单线程环境中,A操作声明在先,B操作声明在后,但是A操作不一定先执行。2、多线程环境中,A操作确实比B操作先执行,但是B操作执行时没看到A操作执行的结果。

情况1、

1
2
3
4
5
6
int A = 0;
int B = 0;
void foo() {
A = 1;
B = 1;
}

编译器优化后,部分汇编代码可能如下:

1
2
3
mov 1, %eax
mov %eax, (B)
mov %eax, (A)

这里编译器做了reorder,但是可以理解为B操作看到了A操作执行结果,这不影响最终结果。

情况2、

1
2
3
4
5
6
7
8
9
10
11
int a = 0;
bool flag = false;
void write_thread() {
a = 1;
flag = true;
}

void read_thread() {
while (!falg);
assert(a == 1);
}

写线程先写给a赋值,再给flag赋值;而读线程先等待falg被写线程赋值后才会继续执行。直觉上看读线程的断言不会触发;但实际上有可能。为什么会这样,可以参考Memory Barriers: a Hardware View for Software Hackers

synchronizes-with

synchronizes-with定义:”Synchronizes-with” is a term invented by language designers to describe ways in which the memory effects of source-level operations – even non-atomic operations – are guaranteed to become visible to other threads

简单来说,synchronizes-with关系是用来确保happen-before关系的,即发生在不同线程间的同步关系,A synchronized B时,表示如果A先于B执行,那么A的执行结果对B一定可见。

C++11中的内存模型

C++11开始提供了六种内存模型

1
2
3
4
5
6
7
8
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;

对于atomic类型变量的操作,都有一个memory_order参数,默认值为memory_order_seq_cst
六种内存模型可以分为三类:1、顺序一直次序(sequentially consistent ordering)memory_order_seq_cst;2、获取-释放次序(acquire-release ordering)memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel;3、松散次序(relaxed ordring)memory_order_relaxed

atomic变量来同步不同线程间的操作,内存模型都是成对使用的。一个线程写atomic变量,另外一个线程读。

“写”可以使用memory_order_seq_cstmemory_order_releasememory_order_relaxed
“读”可以使用memory_order_seq_cstmemory_order_acquirememory_order_consumememory_order_relaxed
“读写”可以使用memory_order_acq_rel

memory_order_seq_cst

这个是atomic变量的默认参数,也是六个内存模型中最严格的,也意味着它的实现代价最高。memory_order_seq_cst简单来说:在不同线程中的操作,先执行的操作都对后执行操作可见,且可见顺序和执行顺序一致,就像在单个线程执行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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
}

memory_order_release –>memory_order_acquire

当前线程写atomic变量使用memory_order_release,这个写操作后的操作不允许reorder到前面。
另一线程读这个变量使用memory_order_acquire,这个读操作前面的操作不允许reorder到后面。
写操作如果发生在读操作前面,那么写操作之前的操作,都对读操作之后的操作可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <thread>
#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;
int data;

void producer()
{

std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}

void consumer()
{

std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}

int main()
{

std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}

memory_order_release –>memory_order_consume

当前线程写atomic变量使用memory_order_release,这个写操作后的操作不允许reorder到前面。
另一线程读这个变量使用memory_order_consume,这个读操作前面的操作不允许reorder到后面。
写操作如果发生在读操作前面,只能确保这个atomic对读操作可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <thread>
#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;
int data;

void producer()
{

std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}

void consumer()
{

std::string* p2;
while (!(p2 = ptr.load(std::memory_order_consume)))
;
assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr
assert(data == 42); // may or may not fire: data does not carry dependency from ptr
}

int main()
{

std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}

memory_order_relaxed

memory_order_relaxed不用来同步,只是用来确保原子操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <vector>
#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> cnt = {0};

void f()
{

for (int n = 0; n < 1000; ++n) {
cnt.fetch_add(1, std::memory_order_relaxed);
}
}

int main()
{

std::vector<std::thread> v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(f);
}
for (auto& t : v) {
t.join();
}
std::cout << "Final counter value is " << cnt << '\n';
// Final counter value is 10000
}

参考

The Happens-Before Relation

The Synchronizes-With Relation

memory_order

Memory Barriers: a Hardware View for Software Hackers

文章目录
  1. 1. 内存模型含义
    1. 1.1. 对象在内存中的布局
    2. 1.2. 多线程访问内存中的对象
      1. 1.2.1. happen-before
    3. 1.3. synchronizes-with
  2. 2. C++11中的内存模型
    1. 2.1. memory_order_seq_cst
    2. 2.2. memory_order_release –>memory_order_acquire
    3. 2.3. memory_order_release –>memory_order_consume
    4. 2.4. memory_order_relaxed
,
#add by kangyabing