文章

C++内存模型

C++内存模型定义多线程中对象的可见性和访问顺序,规范原子操作、防止数据竞争,确保并发程序行为可预测。

C++内存模型

C++内存模型

内存模型涉及两个方面:

  • 内存布局(即对象如何映射到内存)
  • 并发执行中的内存访问规则

并发编程中,特别是底层的原子操作和内存位置管理非常关键。由于C++中的所有对象都和内存地址紧密相关,因此理解对象与内存位置的关系是基础。

对象和内存位置

  • 对象 是C++程序中的基本数据构成单元。
  • 标准中定义类对象为“存储区域”,对象可以有成员函数,甚至有子对象(如数组、类的成员)。
  • 例如,intfloat是基本类型的对象;用户定义的类的实例也是对象。
  • 一些对象(数组、类实例、具有非静态数据成员的类实例)包含多个子对象,但其他对象则没有子对象。

内存位置(memory location)的定义:

  • 一个对象会占据一个或多个内存位置。
  • 每个内存位置可以存储标量类型的对象或其子对象(如unsigned short、指针、相邻位域等)。
  • 对于相邻位域,虽然它们是不同的对象,但仍视为占用相同的内存位置。

例子:结构体对象的内存分解

5-1

假设一个struct包含以下成员:

1
2
3
4
5
6
7
8
struct Example {
    int a;
    int bf1 : 4;
    int bf2 : 4;
    int bf3 : 0;   // 宽度为0的位域
    int bf4 : 8;
    std::string s;
};
  • 整个结构体是一个对象,包含多个子对象(成员变量)。
  • 位域bf1bf2共享一个int的内存位置(4字节/32位)。
  • 宽度为0的位域bf3用于强制下一个位域bf4对齐到下一个int边界,因此bf4拥有独立内存位置。
  • std::string s对象内部可能由多个内存位置组成(如指针和缓冲区等),但对外表现为一个对象。

(注:图中bf3作为命名的0宽度位域可能是示意用,C++标准中未命名的0宽度位域用于对齐。)

需要牢记的原则

  1. 每个变量都是对象,包括成员变量本身也是对象。
  2. 每个对象至少占用一个内存位置
  3. 基本类型对象有确定的内存位置,无论大小、是否相邻或数组元素。
  4. 相邻位域属于相同内存位置的一部分

对象、内存位置与并发

  • 在多线程环境下,线程访问不同内存位置的数据不会产生问题
  • 多个线程访问同一内存位置时必须小心
  • 如果线程仅仅是读取数据,不需要同步。
  • 如果至少有一个线程修改该内存位置,且没有同步机制,则会产生数据竞争,导致未定义行为

如何避免数据竞争?

  1. 互斥量(mutex)
    • 线程在访问共享数据前先锁住互斥量,保证同一时间只有一个线程访问。
  2. 原子操作
    • 对共享数据使用原子类型或原子操作,明确规定访问顺序,保证操作的原子性和内存同步。

如果不规定同一内存地址的访问顺序,那么访问就不是原子的,写写冲突会导致数据竞争和未定义行为。

未定义行为是C++中的“黑洞”,出现未定义行为,程序行为无法预测,可能崩溃、数据损坏,甚至导致硬件异常(极端案例如显示器起火)。

使用原子操作的意义

  • 原子操作不会消除竞争产生的可能性,但能将程序限制在定义良好的行为区域内,避免未定义行为。
  • 这意味着通过原子操作程序是可预测的,但依然需要设计良好的同步逻辑。

修改顺序(Modification Order)

  • 对每个对象,程序执行期间所有线程对该对象的修改都遵守一个全局确定的“修改顺序”
  • 这个顺序在程序初始化阶段确定,但可能与实际执行顺序不同。
  • 所有线程必须遵守这个顺序,否则会出现数据竞争和未定义行为。

非原子类型对象的要求

  • 如果对象不是原子类型,必须通过同步机制(如锁)确保所有线程遵守修改顺序。

原子类型对象的责任

  • 原子操作负责实现同步,使修改顺序被所有线程可见和遵守。

投机执行与修改顺序

  • 线程对特殊输入的读写不能乱序(投机执行受限)。
  • 后续的读操作必须看到最新写入的值。
  • 后续写操作必须发生在前面写操作之后。

其他说明

  • 修改顺序是针对单个对象的顺序。
  • 不同对象的操作间不要求有全局顺序。

小结:什么是原子操作?如何规定顺序?

  • 原子操作是对某个内存位置的操作,保证该操作在并发下不可分割,即不可被中断。
  • 原子操作为多线程环境下的共享数据访问提供了可见性和顺序保证。
  • 通过原子操作,程序建立了访问同一内存位置的“修改顺序”,避免了数据竞争的未定义行为。
本文由作者按照 CC BY 4.0 进行授权