核心要点速览
- 线程 vs 进程:进程是资源分配单位(独立内存),线程是调度单位(共享进程内存),线程通信成本更低
- 线程创建:
std::thread支持函数、Lambda、函数对象三种方式 - 线程管理:
join()等待回收、detach()分离(慎用)、joinable()检查状态 - 线程标识:
std::this_thread::get_id()获取 ID,std::thread::id判断唯一性 - 线程状态:就绪、运行、阻塞、终止
- 常用接口:
std::this_thread命名空间提供sleep_for、sleep_until、yield等工具函数。
一、线程的概念
1. 进程与线程的区别
| 特性 | 进程 (Process) | 线程 (Thread) |
|---|---|---|
| 定义 | 程序的一次执行实例,资源分配的最小单位。 | 进程内的执行单元,调度的最小单位。 |
| 资源 | 独立的代码、数据、堆栈空间。 | 共享进程的代码、全局数据,独立的栈和寄存器。 |
| 隔离 | 地址空间独立 (高隔离性)。 | 共享进程地址空间 (低隔离性)。 |
| 通信 | 进程间通信 (IPC),开销大。 | 直接共享数据,开销小。 |
| 开销 | 创建、切换、销毁开销大。 | 创建、切换、销毁开销小 (轻量化)。 |
2. 线程的优势
- 并发执行多个任务,提升程序响应速度。
- 充分利用多核 CPU 资源,提高 CPU 利用率。
- 比进程更轻量,资源消耗少、调度效率高。
3. 用户线程 vs 内核线程
- 用户线程:用户空间管理,不依赖内核,创建销毁快,但内核无法感知,调度需用户实现。
- 内核线程:内核空间管理,内核直接调度,支持真正并行,但创建销毁开销比用户线程高。
- 常见映射:1:1(内核线程对应用户线程)、M:N(多个用户线程映射到多个内核线程)。
二、线程创建(std::thread)
C++11 std::thread标准化线程操作,跨平台兼容,无需依赖平台 API。
创建方式
- 函数 / 函数指针:
cpp
void func(int a) { /* 线程逻辑 */ }
std::thread t(func, 10);
- Lambda 表达式: 简洁高效,可捕获外部变量(需注意捕获权限与生命周期)。
cpp
std::thread t([]{
std::cout << "Lambda 线程" << std::endl;
});
- 类成员函数: 需传递成员函数指针、对象指针(或引用)及参数。
cpp
class Task {public:
void run(int a) { /* 线程逻辑 */ }};
Task task;
std::thread t(&Task::run, &task, 20); // &task 为对象指针
注意事项
- 线程创建后需立即管理(
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(),且线程未终止)。 - 用途 :避免重复
join或detach导致的未定义行为(如join()已调用的线程)。
3. 僵尸线程与孤儿线程
-
僵尸线程 (Zombie): 子线程已终止,但主线程未调用
join()回收其进程控制块 (PCB) 资源,导致资源泄漏。- 危害: 长期积累会耗尽系统资源。
- 避免: 必须
join()或detach()。
-
孤儿线程 (Orphan): 主线程先于子线程退出,子线程被操作系统托管 给
init进程 (Linux) 或系统进程 (Windows),由托管进程负责回收。- 特点: 不会导致资源泄漏,但其执行逻辑必须是独立的。
4. std::this_thread 常用接口
std::this_thread 命名空间用于操作当前正在执行的线程:
| 接口 | 功能 | 示例 |
|---|---|---|
sleep_for(d) |
让当前线程休眠指定时长,释放 CPU。 | sleep_for(chrono::seconds(1)); |
sleep_until(tp) |
让当前线程休眠到指定时间点。 | 适用于定时任务。 |
yield() |
当前线程主动让出 CPU 给同优先级的就绪线程,自身回到就绪态。 | 适用于提升公平性,避免长时间占用 CPU。 |
get_id() |
获取当前线程的唯一 ID。 | cout << this_thread::get_id(); |
四、线程标识
- 线程 ID :
std::thread::id类型,每个线程有唯一标识(可通过==/!=判断唯一性)。 - 获取方式 :
- 子线程 ID:
std::thread t(func); t.get_id(); - 当前线程 ID:
std::this_thread::get_id();
- 子线程 ID:
- 特殊 ID:默认构造的
std::thread::id表示 "无关联线程"(可判断线程是否有效)。
cpp
std::thread::id default_id; // "无关联线程" ID
if (t.get_id() != default_id) {
// 线程 t 是有效线程
}
五、易错
- 未管理
std::thread:创建后未调用join()/detach(),析构时抛异常。 detach()后访问主线程局部资源:子线程可能在主线程局部变量销毁后执行,导致悬垂引用。- 重复
join():对已join的线程再次调用join(),引发未定义行为(需用joinable()检查)。 - 线程参数按值传递:需传递引用时未用
std::ref,导致拷贝开销或修改无效。 - 线程对象不允许直接赋值或拷贝(编译报错),必须使用
std::move转移所有权。