并发编程:线程
核心要点速览
- 线程 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。
核心创建方式
- 函数 / 函数指针:传递函数地址及参数。
- Lambda 表达式:简洁高效,推荐用于短小逻辑。
- 类成员函数:传递成员函数指针、对象指针及参数。
关键注意事项
- 线程创建后需立即管理(
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()已调用的线程)。
四、线程标识
- 线程 ID:
std::thread::id类型,每个线程有唯一标识(可通过==/!=判断唯一性)。 - 获取方式:
- 子线程 ID:
std::thread t(func); t.get_id(); - 当前线程 ID:
std::this_thread::get_id();
- 子线程 ID:
- 特殊 ID:默认构造的
std::thread::id表示 “无关联线程”(可判断线程是否有效)。
五、易错点
- 未管理
std::thread:创建后未调用join()/detach(),析构时抛异常。 detach()后访问主线程局部资源:子线程可能在主线程局部变量销毁后执行,导致悬垂引用。- 重复
join():对已join的线程再次调用join(),引发未定义行为(需用joinable()检查)。 - 线程参数按值传递:需传递引用时未用
std::ref,导致拷贝开销或修改无效。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 肖恩的博客!
评论

