目录

一、内存管理

二、C++ 内存管理完全指南

一、程序内存五大分区

1. 代码区(Code Segment)

2. 全局 / 静态区(Data Segment)

3. 常量区(Constant Segment)

4. 栈区(Stack)

5. 堆区(Heap)

二、指针与底层内存

1. 基础指针概念

2. 复杂指针类型

3. 指针核心操作

三、动态内存管理

1. malloc /free 用法与特点【容易忘记free和delete是导致动态内存的内存泄漏】

2. new /delete 原理与用法

3. new 与 malloc 核心区别

4. 常见动态内存问题

四、内存对齐与结构体 / 类布局

1. 内存对齐规则

2. 结构体大小计算示例

3. 类对象内存大小规则

五、面向对象内存模型

1. 构造析构与内存初始化

2. 继承内存布局

3. 多态与虚函数表底层

六、const 内存权限

1. const 修饰变量

2. const 修饰指针

3. const 修饰成员函数

七、C++11 及高阶内存

1. 右值引用与移动语义

2. 完美转发

3. 智能指针

(1)unique_ptr

(2)shared_ptr

(3)weak_ptr

4. RAII 内存自动管理

八、常见内存问题与解决方案

1. 内存泄漏

2. 内存越界

3. 悬空指针 / 野指针

4. 多线程共享内存安全

三、操作系统:进程和线程

四、并发多线程(自动驾驶常问)

1. 线程创建方式

2. 互斥锁 mutex、条件变量 condition_variable

3. 死锁产生四个必要条件、怎么避免死锁

4. 原子变量 atomic 作用


一、内存管理

二、C++ 内存管理完全指南

内存管理是 C++ 的核心重难点,直接影响程序的性能、稳定性和安全性。本文基于内存管理思维导图,从基础分区到高阶特性,全面解析 C++ 内存管理的核心知识。

一、程序内存五大分区

C++ 程序运行时,内存会被划分为五大独立区域,各区域的存储内容、分配方式和生命周期均不同,是理解内存管理的基础。

1. 代码区(Code Segment)

  • 存储内容:存放程序编译后的二进制机器指令(如函数执行逻辑、运算指令等)。
  • 核心特性
  • 只读属性:防止程序运行时意外修改指令,保障安全性;
  • 编译时分配:程序编译阶段就确定内存大小,运行时直接加载到内存;
  • 生命周期:从程序启动到程序终止,全程存在。

2. 全局 / 静态区(Data Segment)

  • 存储内容:全局变量(定义在函数外部的变量)、static 修饰的静态变量(包括全局静态变量和局部静态变量)。
  • 核心特性
  • 编译时分配内存:变量的内存空间在程序编译阶段就已确定,而非运行时;
  • 默认初始化:未显式赋值的变量会被默认初始化为 0(全局变量)或对应类型的默认值(静态变量);
  • 生命周期:程序启动时分配内存,程序终止时才释放,全程贯穿程序运行;
  • 共享性:全局变量可被整个程序的所有文件访问(需配合 extern 声明),静态变量仅在其定义的作用域内访问。

3. 常量区(Constant Segment)

  • 存储内容:字符串常量(如 "hello world")、const 修饰的全局常量(const 修饰的局部常量可能存于栈区,取决于编译器优化)。
  • 核心特性
  • 只读属性:不可通过指针或直接赋值修改,强行修改会导致程序崩溃(未定义行为);
  • 编译时分配:内存大小在编译阶段确定,运行时加载;
  • 生命周期:同程序生命周期,程序终止后释放;
  • 字符串常量池优化:相同的字符串常量会被合并存储,避免重复占用内存(如 const char* s1 = "abc"; const char* s2 = "abc"; 中 s1 和 s2 指向同一块内存)。

4. 栈区(Stack)

  • 存储内容:局部变量(函数内部定义的变量)、函数参数(形参)、函数调用时的栈帧(包含返回地址、寄存器状态等)。
  • 核心特性
  • 自动分配与释放:变量随作用域创建(如进入函数时),随作用域结束(如函数返回时)自动释放,无需手动管理;
  • 内存空间小:栈的大小通常固定(Windows 下默认约 1MB,Linux 下默认约 8MB),超出会导致栈溢出(Stack Overflow);
  • 分配效率高:栈内存的分配通过 “栈指针移动” 实现,无需复杂的内存分配算法,速度快于堆区;
  • 存储顺序:遵循 “先进后出(FILO)” 原则,新变量压入栈顶,释放时从栈顶弹出;
  • 未初始化随机值:局部变量若未显式赋值,其值为随机垃圾值(不默认初始化)。

5. 堆区(Heap)

  • 存储内容:程序运行时手动申请的动态内存(通过 new/malloc 申请的内存块)。
  • 核心特性
  • 动态分配:内存大小可在程序运行时根据需求确定,灵活度高;
  • 手动管理:需通过 delete/free 手动释放,若未释放会导致内存泄漏(程序运行期间内存持续占用,直至程序终止);
  • 内存空间大:堆区是程序可使用的最大内存区域(取决于系统物理内存和虚拟内存配置);
  • 分配效率低:堆内存分配需通过内存管理算法查找空闲内存块,速度慢于栈区;
  • 未初始化随机值:申请的堆内存若未显式初始化,其值为随机垃圾值。

二、指针与底层内存

指针是 C++ 操作内存的核心工具,本质是存储内存地址的变量。理解指针的底层逻辑,是掌握内存管理的关键。

1. 基础指针概念

  • 定义:指针变量存储的是目标变量的内存地址,通过指针可间接访问或修改目标变量的值。
  • 核心类型
  • 空指针(nullptr):C++11 引入的标准空指针,指向 “无有效地址” 的位置,用于避免野指针(推荐使用,替代旧版 NULL);
  • 野指针:未初始化、已释放或越界的指针,指向不确定的内存区域,访问野指针会导致程序崩溃(未定义行为)。

2. 复杂指针类型

  • 二级指针:指向指针的指针,用于存储指针变量的内存地址(如 int a = 10; int* p = &a; int** pp = &p;),常用于函数参数中修改指针的值;
  • 指针数组:数组的每个元素都是指针(如 int* arr[3];),数组大小固定,元素是指针类型;
  • 数组指针:指向数组的指针(如 int (*p)[3];),指针指向整个数组,而非数组元素,常用于多维数组操作;
  • 函数指针:指向函数的指针,存储函数的入口地址(如 int (*func)(int, int);),常用于回调函数、函数表等场景。

3. 指针核心操作

  • 指针运算
  • 算术运算:指针 + n 表示指向当前地址后第 n 个元素的地址(步长为指针指向类型的大小,如 int* p 中 p+1 偏移 4 字节);
  • 关系运算:指针可比较大小(基于内存地址的高低),常用于数组遍历边界判断;
  • 内存越界指针访问超出其指向内存区域的地址(如数组下标越界、堆内存越界),会导致内存污染或程序崩溃;
  • 指针与引用的底层区别
  • 内存占用:指针变量本身占用内存(32 位系统 4 字节,64 位系统 8 字节),引用本质是指针的 “别名”,不占用额外内存;
  • 初始化:指针可初始化为空指针,引用必须初始化且绑定到有效变量;
  • 可修改性:指针可重新指向其他变量,引用一旦绑定无法更改绑定对象;
  • 安全性:引用比指针更安全,避免野指针问题,但灵活性低于指针。

三、动态内存管理

C++ 提供两种动态内存管理方式:C 语言兼容的 malloc/free 和 C++ 专属的 new/delete,核心用于堆区内存的申请与释放。

1. malloc /free 用法与特点【容易忘记free和delete是导致动态内存的内存泄漏】

  • malloc: 
    •  函数原型:void* malloc(size_t size);,接收内存大小(字节数),返回指向申请内存的 void* 指针;
    • 核心特点:仅分配内存,不初始化内存(内存值为随机垃圾值),不调用构造函数;
    • 注意事项:需显式转换指针类型(如 int* p = (int*)malloc(4);),申请失败返回 NULL。
  • free
  • 函数原型:void free(void* ptr);,接收 malloc 申请的指针,释放对应的堆内存;
  • 核心特点:仅释放内存,不调用析构函数,释放后指针变为野指针(需手动置为 nullptr);
  • 注意事项:不可重复释放同一指针,不可释放非 malloc 申请的指针(如栈指针、常量区指针)。

2. new /delete 原理与用法

  • new
    •  核心流程:先调用 operator new 函数分配堆内存,再调用目标类型的构造函数初始化对象;
    • 用法示例:
      • 注意事项:申请失败时抛出 std::bad_alloc 异常(而非返回 NULL),无需显式类型转换。
      • 数组对象:int* arr = new int[3]{1,2,3};(分配 12 字节内存,初始化数组元素);
      • 单个对象:int* p = new int(10);(分配 4 字节内存,初始化值为 10);
  • delete
  •  核心流程:先调用目标类型的析构函数销毁对象,再调用 operator delete 函数释放堆内存;
  •  用法示例:
  •  单个对象:delete p;(调用析构函数 + 释放内存);
  •  数组对象:delete[] arr;(必须用 delete[],否则仅销毁第一个元素,导致内存泄漏);
  •  注意事项:释放后指针变为野指针,需手动置为 nullptr,不可重复释放。

3. new 与 malloc 核心区别

对比维度

new

malloc

本质

C++ 运算符,支持对象初始化

C 语言函数,仅分配内存

类型转换

无需显式转换

需显式转换为目标类型指针

构造 / 析构

自动调用构造函数 / 析构函数

不调用,仅分配 / 释放内存

申请失败处理

抛出 std::bad_alloc 异常

返回 NULL

数组支持

支持 new[] 分配数组

需手动计算数组总字节数

重载扩展

可重载 operator new 自定义分配

不可重载,功能固定

4. 常见动态内存问题

  • 内存泄漏:堆内存申请后未释放,导致内存持续占用,程序运行时间越长,占用内存越多,最终可能导致系统内存耗尽;
  • 典型场景:函数中 new 申请内存后,未在返回前 delete;异常抛出导致 delete 语句未执行;
  • 避免方式:使用智能指针(RAII 机制)自动管理内存;养成 “申请即释放” 的习惯使用内存检测工具(如 Valgrind)。
  • 野指针:指针未初始化、已释放或越界,指向不确定内存区域;
  • 避免方式:指针初始化时置为 nullptr;释放后再次置为 nullptr;不访问已释放的指针。
  • 内存碎片:频繁申请和释放大小不一的堆内存,导致内存中存在大量零散的空闲内存块(无法被有效利用),降低内存使用效率;
  • 缓解方式:使用内存池(预先分配大块内存,按需拆分使用);尽量申请连续的大块内存;避免频繁申请 / 释放小块内存。

四、内存对齐与结构体 / 类布局

内存对齐是编译器为了提高程序运行效率,对结构体、类的成员变量存储地址进行的规则化排列,本质是 “空间换时间”。

1. 内存对齐规则

  • 基础规则
  1. 结构体 / 类的第一个成员变量的偏移量(相对于结构体起始地址的距离)为 0;
  2. 后续每个成员变量的偏移量,必须是该成员变量大小的整数倍(若有编译器指定的对齐系数,则取成员大小和对齐系数的较小值的整数倍);
  3. 结构体 / 类的总大小,必须是所有成员变量大小的最大公约数的整数倍(或对齐系数的整数倍,取两者较大值)。
  • 对齐系数:编译器默认对齐系数(如 GCC 默认 4 字节,VS 默认 8 字节),可通过 #pragma pack(n) 手动指定(n 为 2 的幂,如 1、2、4、8)。

2. 结构体大小计算示例

// 示例1:默认对齐(GCC,对齐系数4)

struct A {

char c; // 大小1,偏移0(0是1的整数倍)

int i; // 大小4,偏移需是4的整数倍 → 偏移4(填充3字节)

short s; // 大小2,偏移需是2的整数倍 → 偏移8(4+4=8)

};

// 总大小:8+2=10,需是最大成员大小(4)的整数倍 → 12字节(填充2字节)

// 示例2:指定对齐系数2(#pragma pack(2))

struct B {

char c; // 偏移0

int i; // 偏移需是2的整数倍 → 偏移2(填充1字节)

short s; // 偏移2+4=6(6是2的整数倍)

};

// 总大小:6+2=8,是最大成员大小(4)和对齐系数(2)的较大值(4)的整数倍 → 8字节

3. 类对象内存大小规则

  • 类对象的内存大小 = 所有非静态成员变量的大小之和 + 内存对齐填充字节 + 虚函数表指针大小(若类含虚函数);
  • 核心注意点:
  • 静态成员变量不占用类对象内存(存于全局 / 静态区);
  • 成员函数(普通函数、静态函数、虚函数)不占用类对象内存(函数指令存于代码区);
  • 虚函数表指针(vptr):若类含虚函数,编译器会为类对象添加一个指针(32 位 4 字节,64 位 8 字节),指向虚函数表(vtable),用于实现多态。

五、面向对象内存模型

C++ 面向对象的核心特性(封装、继承、多态),其底层实现依赖内存布局的设计。

1. 构造析构与内存初始化

  • 构造函数:对象创建时自动调用,用于初始化对象的非静态成员变量,分配对象所需的资源(如堆内存、文件句柄);
  • 内存层面:构造函数执行时,对象的内存已分配(栈区或堆区),构造函数的作用是填充该内存区域的成员变量值;
  • 默认构造函数:无参数的构造函数,若未显式定义,编译器会自动生成(但在某些场景下会被抑制,如定义了带参数的构造函数)。
  • 析构函数:对象销毁时自动调用,用于释放对象占用的资源(如堆内存、文件句柄);
  • 内存层面:析构函数执行后,对象的内存才会被释放(栈区自动释放,堆区需手动 delete);
  • 虚析构函数:若类作为基类,需将析构函数声明为 virtual,否则删除基类指针指向的派生类对象时,仅调用基类析构函数,导致派生类资源泄漏。

2. 继承内存布局

  • 单继承:派生类对象的内存 = 基类成员变量 + 派生类成员变量,按继承顺序排列,遵循内存对齐规则;
  • 示例:
class Base { int a; };

class Derived : public Base { int b; };

// Derived对象内存布局:a(偏移0)→ b(偏移4),总大小8字节(对齐后)
  • 多继承:派生类对象的内存 = 第一个基类成员 + 第二个基类成员 + ... + 派生类成员,可能存在内存冗余(如多个基类有相同成员);
  • 虚继承:用于解决多继承中的菱形继承问题(多个派生类继承自同一基类,再被一个类多重继承),通过 “虚基类表” 优化内存,确保基类成员仅存储一次;
  • 内存层面:虚继承的派生类会添加一个虚基类表指针,指向虚基类表,用于定位虚基类成员的地址。

3. 多态与虚函数表底层

  • 虚函数表(vtable):每个含虚函数的类(或其派生类)会有一个全局唯一的虚函数表,存储该类所有虚函数的入口地址;
  • 虚函数表指针(vptr):每个含虚函数的类对象,会在内存布局的起始位置(或固定位置)添加一个 vptr,指向该类的 vtable;
  • 多态实现原理
  • 基类指针指向派生类对象时,指针访问虚函数时,会通过对象的 vptr 找到派生类的 vtable,调用对应的派生类虚函数;
  • 非虚函数直接通过类名定位函数地址,不经过 vtable,无法实现多态。

六、const 内存权限

const 关键字用于限制变量的修改权限,其底层实现与内存分区、编译器优化密切相关。

1. const 修饰变量

  • const 全局变量:存于常量区,只读属性,不可修改;
  • const 局部变量
  • 栈区存储(未被编译器优化时):可通过指针间接修改(但属于未定义行为,可能导致程序异常);
  • 编译器优化为常量(如 const int a = 10;):可能直接嵌入指令,不占用栈内存,修改指针无效。

2. const 修饰指针

  • const 修饰指针指向的内容(const int* p):指针指向的变量不可修改,指针本身可重新指向其他地址;
  • const 修饰指针本身(int* const p):指针本身不可重新指向其他地址,指针指向的变量可修改;
  • const 同时修饰两者(const int* const p):指针本身和指向的变量均不可修改。

3. const 修饰成员函数

  • 声明格式:void func() const;,表示该成员函数不会修改类对象的非静态成员变量;
  • 底层实现:const 成员函数的 this 指针是 const 类名* 类型,限制了对成员变量的修改;
  • 注意事项:const 成员函数可访问 const 成员变量和非 const 成员变量,但不可修改;非 const 成员函数可调用 const 成员函数,const 成员函数不可调用非 const 成员函数。

七、C++11 及高阶内存

C++11 引入了一系列内存优化特性,解决传统动态内存管理的痛点(如内存泄漏、拷贝开销大)。

1. 右值引用与移动语义

  • 右值引用:用 && 声明,专门绑定到右值(临时对象、字面量等),如 int&& a = 10;;
  • 移动语义:通过移动构造函数和移动赋值运算符,将一个对象的资源(如堆内存)“转移” 到另一个对象,而非拷贝,减少内存拷贝开销;
    • 移动构造函数原型:类名(类名&& other);,通常会将 other 的资源指针置为 nullptr,避免释放时重复释放;
    • 适用场景:函数返回局部对象、容器扩容时元素转移等。

2. 完美转发

  • 核心目的:在模板函数中,将参数原封不动地转发给其他函数,保留参数的左值 / 右值属性;
  • 实现方式:通过 std::forward 函数配合右值引用模板,如 template <typename T> void func(T&& args) { other_func(std::forward<T>(args)); }。

3. 智能指针

智能指针是基于 RAII(资源获取即初始化)思想的类模板,自动管理堆内存,避免内存泄漏。

(1)unique_ptr
  • 核心特性:独占式所有权,同一时间仅一个 unique_ptr 指向堆内存,不可拷贝,仅可移动;
  • 用法示例:
std::unique_ptr<int> p1 = std::make_unique<int>(10);

std::unique_ptr<int> p2 = std::move(p1); // 移动所有权,p1变为nullptr

// p1 = p2; 错误,不可拷贝
  • 适用场景:单个对象的独占式管理,无需共享所有权。
(2)shared_ptr
  • 核心特性:共享式所有权,多个 shared_ptr 可指向同一堆内存,通过引用计数管理生命周期;
  • 引用计数:每个 shared_ptr 指向的堆内存会维护一个引用计数,新增一个 shared_ptr 时计数 + 1,销毁一个时计数 - 1,计数为 0 时自动释放内存;
  • 用法示例:

std::shared_ptr<int> p1 = std::make_shared<int>(10);

std::shared_ptr<int> p2 = p1; // 引用计数变为2
  • 注意事项:避免循环引用(如 A 的 shared_ptr 指向 B,B 的 shared_ptr 指向 A),导致引用计数无法归零,内存泄漏(需用 weak_ptr 解决)。
(3)weak_ptr
  • 核心特性:弱引用,不占引用计数,用于解决 shared_ptr 的循环引用问题;
  • 用法:通过 shared_ptr 构造 weak_ptr,需通过 lock() 方法获取 shared_ptr 后才能访问对象(避免对象已释放);
  • 示例:

std::shared_ptr<int> p = std::make_shared<int>(10);

std::weak_ptr<int> wp = p; // 不增加引用计数

if (auto sp = wp.lock()) { // 若对象存在,sp是shared_ptr,计数+1

std::cout << *sp << std::endl;

}

4. RAII 内存自动管理

RAII 是一种编程范式,核心思想是 “资源获取时初始化对象,对象销毁时释放资源”;

  • 智能指针是 RAII 的典型应用,此外还可自定义 RAII 类管理其他资源(如文件句柄、锁);
  • 示例:
class FileRAII {

private:

FILE* file;

public:

FileRAII(const char* path) { file = fopen(path, "r"); }

~FileRAII() { if (file) fclose(file); } // 自动关闭文件

};

八、常见内存问题与解决方案

1. 内存泄漏

  • 原因:堆内存申请后未释放,如 new 后未 delete、malloc 后未 free、智能指针循环引用等;
  • 解决方案
    • 优先使用智能指针(unique_ptr/shared_ptr)自动管理内存;
    • 避免手动管理大块堆内存,使用 STL 容器(如 vector)替代;
    • 使用内存检测工具(Valgrind、Visual Studio 内存诊断)排查泄漏点;
    • 遵循 “谁申请谁释放” 的原则,明确内存释放责任。

2. 内存越界

  • 原因指针访问超出其指向的内存区域,如数组下标越界、堆内存越界写入;
  • 解决方案
  • 数组遍历使用迭代器或范围 for 循环,避免手动计算下标;
  • 堆内存操作时,严格控制写入大小,不超过申请的内存;
  • 使用边界检查工具(如 GCC 的 -fsanitize=address 编译选项)检测越界。

3. 悬空指针 / 野指针

  • 原因:指针未初始化、已释放后未置空、指针指向的对象已销毁;
  • 解决方案
    • 指针初始化时默认置为 nullptr;
    • 释放指针后(delete/free),立即置为 nullptr;
    • 避免返回局部变量的指针或引用;
    • 使用前检查指针是否为 nullptr。

4. 多线程共享内存安全

  • 原因多个线程同时读写共享内存,导致数据竞争(未定义行为);
  • 解决方案
  • 使用互斥锁(std::mutex)保护共享内存,确保同一时间仅一个线程访问;
  • 使用原子类型(std::atomic)替代普通变量,避免数据竞争;
  • 减少共享内存的使用,采用消息传递(如队列)实现线程间通信。

三、操作系统:进程和线程

        进程(Process)和线程(Thread),是操作系统里非常重要的两个概念。

        进程是资源分配的基本单位。进程的创建、终止、调度、同步以及进程间的通信,都是由操作系统负责的。应用程序的运行,包括操作系统本身核心功能的运行,都是以进程的形式存在。

        每个进程都包括程序的代码、数据、状态,以及操作系统为该程序分配的资源(如内存空间、文件句柄、网络端口等)。操作系统通过进程管理,来确保各个进程能够高效、安全地共享CPU时间。

我们使用“Ctrl+Alt+Del”快捷键调出Windows的任务管理器,就可以看到很多的进程:

图片

任务管理器

        线程,则是操作系统进行运算调度的最小单位。

        线程进程更低一级,是进程内的一个可以独立调度和指派的执行单元。

        一个进程中可以有多个线程,共享相同的内存空间和资源,可以更容易地进行通信和数据共享。

图片

        进程与线程

        例如你启动了一个浏览器程序,那么,操作系统就会开启一个相应的进程。这个进程里面,又会有多个线程,如HTTP请求线程、事件响应线程、渲染线程等。

        如果你关闭这个浏览器程序,从任务管理器可以看到,这个进程和对应的线程都没有了。当然,你也可以在任务管理器里,直接右键关闭某个进程,程序也就强制退出了。Linux里干掉一个进程,用的命令就是“kill(杀掉)”。

        线程是操作系统发展到后期才引入的。它进一步提供了程序执行的并发性,提高了系统的效率。

        进程和线程,都可以包括执行态、就绪态、阻塞态等状态。对进程和线程进行管理,本质上是为了实现对CPU资源的分配调度。

图片

        进程的状态变化

        需要注意的是,一个程序可以对应一个或多个进程。而一个进程同样可以对应一个或多个程序(虽然比较罕见)。

        其次,是内存管理

        以前我们多次提到过冯·诺依曼架构。程序要从硬盘到内存,才能够被运算器(CPU)处理。每个程序都有足够的内存空间,才能够确保正常运行。

图片

        冯·诺依曼架构

        运行之后,内存也需要被及时释放,才能让别的程序能够继续占用。

        内存的分配和回收,也是操作系统负责的。

        除了内存分配之外,操作系统还要负责进行内存保护(确保每道程序都只在自己的内存区中运行,进程间不会互相干扰)、地址映射(将程序装入内存运行时,需要将逻辑地址转化成内存单元所限定的物理地址)、内存扩充(借助于虚拟存储技术,从逻辑上去扩充内存容量)等工作。

四、并发多线程(自动驾驶常问)

1. 线程创建方式

标准答案:C++11后常用4种,重点记前3种:

  • 用函数指针:std::thread t(func, 参数); (func是线程要执行的函数)。

  • 用lambda表达式:std::thread t([](){ /* 线程执行逻辑 */ });

  • 用类的成员函数:std::thread t(&类名::成员函数, &对象, 参数);

  • 用可调用对象(如函数对象):自定义类,重载()运算符,传入thread。

补充:线程创建后,需调用join()(等待线程结束)或detach()(分离线程,线程独立运行),否则程序会崩溃。

2. 互斥锁 mutex、条件变量 condition_variable

标准答案:

  • 互斥锁 mutex:用于保护多线程共享资源,避免竞争条件(多个线程同时读写共享资源);用法:std::mutex mtx; mtx.lock(); (临界区代码); mtx.unlock(); 推荐用std::lock_guard(自动上锁、解锁,避免忘记unlock)。

  • 条件变量 condition_variable:用于线程间通信,实现线程的等待和唤醒;常与互斥锁配合使用,如线程A等待某个条件满足,线程B满足条件后唤醒线程A;常用接口:wait()(等待)、notify_one()(唤醒一个线程)、notify_all()(唤醒所有线程)。

3. 死锁产生四个必要条件、怎么避免死锁

标准答案:

死锁四个必要条件(必须同时满足才会产生):① 互斥条件:资源只能被一个线程占用;② 请求与保持条件:线程持有一个资源,又请求另一个资源;③ 不可剥夺条件:资源不能被强制剥夺;④ 循环等待条件:多个线程互相等待对方的资源。

避免死锁方法:① 按固定顺序获取资源(如所有线程都先获取锁A,再获取锁B);② 避免长时间持有锁,获取锁后尽快释放;③ 用std::lock()同时获取多个锁,避免循环等待;④ 用try_lock()尝试获取锁,失败则释放已持有锁。

4. 原子变量 atomic 作用

标准答案:用于多线程共享变量的原子操作,保证操作的不可分割性(不会被其他线程打断),避免竞争条件,无需手动加锁;常用类型:std::atomic<int>、std::atomic<bool>;适用场景:多线程计数、标志位判断(如自动驾驶中,多线程更新目标计数)。

补充:atomic比mutex效率高,适合简单的读写操作,复杂操作仍需用mutex。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐