核心要点速览

  • 线程 vs 进程:进程是资源分配单位(独立内存),线程是调度单位(共享进程内存),线程通信成本更低
  • 线程创建:std::thread支持函数、Lambda、函数对象三种方式
  • 线程管理:join()等待回收、detach()分离(慎用)、joinable()检查状态
  • 线程标识:std::this_thread::get_id()获取 ID,std::thread::id判断唯一性
  • 线程状态:就绪、运行、阻塞、终止

一、线程的基本概念

1. 线程与进程

  • 进程:程序的一次执行实例,拥有独立内存空间(代码、数据、堆栈),是资源分配的最小单位。
  • 线程:进程内的执行单元,共享进程的代码、全局数据等资源,拥有独立的栈和寄存器,是调度的最小单位。
  • 核心区别:
    • 资源隔离:进程间地址空间独立,线程间共享进程内存。
    • 通信成本:进程间通信(IPC)需跨地址空间(成本高),线程间直接共享数据(成本低)。
    • 轻量化:线程比进程更轻量,创建、切换、销毁开销更小。

2. 线程的优势

  • 并发执行多个任务,提升程序响应速度。
  • 充分利用多核 CPU 资源,提高 CPU 利用率。
  • 比进程更轻量,资源消耗少、调度效率高。

3. 用户线程 vs 内核线程

  • 用户线程:用户空间管理,不依赖内核,创建销毁快,但内核无法感知,调度需用户实现。
  • 内核线程:内核空间管理,内核直接调度,支持真正并行,但创建销毁开销比用户线程高。
  • 常见映射:1:1(内核线程对应用户线程)、M:N(多个用户线程映射到多个内核线程)。

二、线程创建(std::thread)

C++11 std::thread标准化线程操作,跨平台兼容,无需依赖平台 API。

核心创建方式

  1. 函数 / 函数指针:传递函数地址及参数。
  2. Lambda 表达式:简洁高效,推荐用于短小逻辑。
  3. 类成员函数:传递成员函数指针、对象指针及参数。

关键注意事项

  • 线程创建后需立即管理(join()detach()),否则析构时抛出std::terminate异常。
  • 传递参数时,默认按值拷贝,需传递引用时用std::ref/std::cref(避免拷贝开销或悬垂引用)。

三、线程生命周期与管理

1. 线程状态

  • 就绪:已创建,等待 CPU 调度(具备运行条件)。
  • 运行:占用 CPU,执行线程逻辑。
  • 阻塞:因等待资源(如锁、IO)暂停执行,释放 CPU。
  • 终止:线程执行完毕或被强制终止,资源等待回收。

2. 线程管理函数

(1)join()

  • 功能:主线程阻塞,等待子线程执行完毕后再继续,回收子线程资源(避免 “僵尸线程”)。
  • 限制:一个线程只能调用一次join(),调用后joinable()返回false

(2)detach()

  • 功能:主线程与子线程分离,子线程后台运行,主线程不等待。
  • 风险:子线程依赖的主线程资源(如局部变量)可能提前释放,导致悬垂引用(崩溃风险)。
  • 适用场景:子线程逻辑独立,不依赖主线程局部资源,且无需主线程等待。

(3)joinable()

  • 功能:检查线程是否可join(未调用join()/detach(),且线程未终止)。
  • 用途:避免重复joindetach导致的未定义行为(如join()已调用的线程)。

四、线程标识

  • 线程 ID:std::thread::id类型,每个线程有唯一标识(可通过==/!=判断唯一性)。
  • 获取方式:
    • 子线程 ID:std::thread t(func); t.get_id();
    • 当前线程 ID:std::this_thread::get_id();
  • 特殊 ID:默认构造的std::thread::id表示 “无关联线程”(可判断线程是否有效)。

五、易错点

  1. 未管理std::thread:创建后未调用join()/detach(),析构时抛异常。
  2. detach()后访问主线程局部资源:子线程可能在主线程局部变量销毁后执行,导致悬垂引用。
  3. 重复join():对已join的线程再次调用join(),引发未定义行为(需用joinable()检查)。
  4. 线程参数按值传递:需传递引用时未用std::ref,导致拷贝开销或修改无效。