内存模型

来自cppreference.com
< cpp‎ | language

为 C++ 抽象机的目的定义计算机内存存储的语义。

可用于 C++ 程序的内存是一或多个相接的字节序列。内存中的每个字节拥有唯一的地址

目录

[编辑] 字节

字节是最小的可寻址内存单元。它被定义为相接的位序列,大到足以保有任何 UTF-8 编码单元( 256 个相异值)和 (C++14 起)基本执行字符集(要求为单字节的 96 个字符)的任何成员。类似 C , C++ 支持 8 位或更大的字节。

charunsigned charsigned char 类型把一个字节用于存储和值表示。字节中的位数可作为 CHAR_BITstd::numeric_limits<unsigned char>::digits 访问。

[编辑] 内存位置

内存位置

  • 一个标量类型(算术类型、指针类型、枚举类型或 std::nullptr_t )对象
  • 或非零长位域的最大相接序列

注意:各种语言特性,例如引用虚函数,可能涉及到程序不可访问,但为实现所管理的额外内存位置。

struct S {
    char a;     // 内存位置 #1
    int b : 5;  // 内存位置 #2
    int c : 11, // 内存位置 #2 (延续)
          : 0,
        d : 8;  // 内存位置 #3
    struct {
        int ee : 8; // 内存位置 #4
    } e;
} obj; // 对象 'obj' 由 4 个分离的内存位置组成

[编辑] 线程与数据竞争

执行线程是程序中的控制流,它始于 std::thread::threadstd::async 或以其他方式所做的顶层函数调用。

任何线程都能潜在地访问程序中的任何对象(拥有自动或线程局域存储期的对象仍可为另一线程通过指针或引用访问)。

始终允许不同的执行线程同时访问(读和写)不同的内存位置,而无冲突或同步要求。

一个表达式的求值写入内存位置,而另一求值读或写同一内存位置时,称这些表达式冲突。拥有二个冲突求值的程序有数据竞争,除非

  • 两个求值都在同一线程上,或同一信号处理函数中执行,或
  • 两个冲突求值都是原子操作(见 std::atomic ),或
  • 一个冲突求值先发生于( happens-before )另一个(见 std::memory_order

若出现数据竞争,则程序的行为未定义。

(特别是, std::mutex 的释放同步于,从而先发生于另一线程取得同一 mutex ,这使得可以用互斥锁防止数据竞争)

int cnt = 0;
auto f = [&]{cnt++;};
std::thread t1{f}, t2{f}, t3{f}; // 未定义行为
std::atomic<int> cnt{0};
auto f = [&]{cnt++;};
std::thread t1{f}, t2{f}, t3{f}; // OK

[编辑] 内存顺序

线程在从内存位置读取值时,它可能看到初值、同一线程所写入的值或另一线程写入的值。线程所作的写入对其他线程变为可见的顺序上的细节,见 std::memory_order

[编辑] 向前进展

[编辑] 免妨碍

只有一个不在标准库函数中阻塞的线程执行免锁的原子函数时,保证该执行完成(所有标准库免锁操作均为免妨碍)。

[编辑] 免锁

一或多个免锁原子函数同时运行时,保证其中至少一个完成(所有标准库免锁操作均为免锁——确保其他线程不能不确定地活锁它们,例如以连续窃取缓存线的方式,是实现的工作)。

[编辑] 进展保证

合法的 C++ 程序中,每个线程最终要做下列之一:

  • 终止
  • 调用 I/O 库函数
  • 通过 volatile 泛左值进行访问
  • 进行原子操作或同步操作

没有线程能永远执行,而不做任何这些可观察行为。

注意这表示有无限递归或无限循环(无论是实现为 for 语句 或是用 goto 还是其他方式)的程序拥有未定义行为。这允许编译器移除所有无可观察行为的循环,而不必证明他们终将终止。

若线程执行了上述步骤之一( I/O 、 volatile 、原子或同步)、阻塞于标准库函数中,或调用由于非阻塞的并发线程而未完成的原子免锁函数,则称它取得进展( make progress )

并发向前进展

若线程提供并发向前进展保证( concurrent forward progress guarantee ),则只要它尚未终止,就将在有限量的时间内取得进展 (定义如上),无关乎其他线程(若存在)是否在取得进展。

标准鼓励但不要求主线程和 std::thread 所开始的线程提供并发向前进展保证。

并行向前进展

若线程提供并行向前进展保证( parallel forward progress guarantee ),则若线程尚未执行任何执行步骤( I/O 、 volatile 、原子或同步),就不要求实现保证该线程终将取得进展,但一旦此线程开始执行步骤,则它提供并发向前进展保保证(此规则描述线程池中以任意顺序执行的线程)。

弱并行向前进展

若线程提供弱并行向前进展保证( weakly parallel forward progress guarantee ),则不保证它终将取得进展,无关乎其他线程是否取得进展。

仍然能以向前进展保证委托,用阻塞保证这种线程取得进展:若线程 P 以此方式在线程集 S 的完成上阻塞,则 S 中至少有一个线程将提供等于或强于 P 的向前进展保证。一旦该线程完成,则类似地强化 S 中的另一线程。一旦集合为空,则将解阻塞 P 。

来自 C++ 标准库的并行算法以向前保证委托,阻塞在库所管理线程的未指定集合的完成上。

(C++17 起)

[编辑] 参阅

内存模型C 文档