std::thread 的创建方式、join 与 detach 的语义、传参陷阱与常见坑。同步机制(互斥锁、条件变量、原子操作)见另一篇并发笔记。
核心概念速查#
| 概念 | 说明 | 关键点 |
|---|
| 进程 | 独立运行的程序实例 | 有独立内存空间 |
| 线程 | 进程内的执行单元 | 共享进程内存 |
| std::thread | C++11 线程库 | #include <thread> |
| join | 等待线程结束 | 阻塞当前线程 |
| detach | 分离线程 | 后台运行,不再可 join |
进程 vs 线程#
| 维度 | 进程 | 线程 |
|---|
| 内存空间 | 独立 | 共享 |
| 通信 | 管道/队列/文件 | 直接共享变量(需同步) |
| 切换开销 | 大 | 小 |
| 创建开销 | 大 | 小 |
| 崩溃影响 | 只影响自身 | 可能带崩整个进程 |
| 适用场景 | 隔离、分布式 | 高并发、I/O 密集 |
创建线程#
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread\n";
}
int main() {
// 方式1: 无参函数
std::thread t1(hello);
// 方式2: lambda
std::thread t2([]{
std::cout << "Hello from lambda\n";
});
// 方式3: 带参函数
auto print_num = [](int n) {
std::cout << "Number: " << n << "\n";
};
std::thread t3(print_num, 42);
t1.join();
t2.join();
t3.join();
return 0;
}
join 与 detach#
| 操作 | 行为 | 何时用 |
|---|
join() | 等待线程结束才继续 | 需要线程结果时 |
detach() | 立即返回,线程后台跑 | 结果不关心时(慎用) |
陷阱:线程要么 join 要么 detach,都不做 → 析构时 std::terminate() 崩溃。detach 的线程访问已销毁的对象 → 未定义行为。
传参陷阱#
#include <thread>
void modify(int& x) { x = 100; } // 想通过引用改外部变量
int main() {
int value = 5;
// std::thread t1(modify, value); // 编译错误:参数被拷贝,绑不上 int&
std::thread t2(modify, std::ref(value)); // 正确:显式包引用
t2.join();
return 0;
}
std::thread 默认把实参拷贝进线程上下文,所以传引用必须用 std::ref 显式包装;同理,把临时对象或局部对象的指针交给线程,线程还没跑对象就可能已析构。
常见坑#
| 问题 | 原因 | 解决 |
|---|
std::terminate 崩溃 | 线程未 join/detach | 确认每个线程都 join 或 detach |
| 数据竞争 | 多线程同时写共享变量 | 加锁 |
| 悬空引用 | 线程访问已析构对象 | 传值 / std::ref / 智能指针 |
| 乱序输出 | cout 不是线程安全 | 加互斥锁或原子输出 |
- 线程的默认行为是"不等待":主线程退出,程序结束,残余线程被强杀。join 是"我要等你干完"的显式承诺。
- 共享内存是把双刃剑:省去进程间通信的麻烦,但引入同步问题,这是并发编程的核心难题。
- C++11 之后才"官方"支持线程:之前靠 pthread;现在跨平台直接
std::thread。
自测清单#
- 理解进程与线程的区别
- 能用
std::thread 创建线程(函数 / lambda / 带参) - 理解 join 与 detach 的差异,知道不调用会怎样
- 知道传引用要用
std::ref - 意识到共享数据需要同步