std::thread 构造后必须 detach 或 join,否则析构时调用 std::terminate() 导致崩溃;传参需显式用 std::ref 或 std::move;共享数据须用 mutex 或 atomic 同步,避免未定义行为。

std::thread 构造后必须 detach 或 join
不处理的 std::thread 对象在析构时会调用 std::terminate(),程序直接崩溃,错误信息通常是 std::thread::~thread(): Resource deadlock avoided 或更简短的 terminate called without an active exception。
常见错误是写完 std::thread t{func} 就不管了,尤其在函数提前 return、异常抛出、或循环中反复创建线程时极易漏掉。
- 用
t.join()等待线程结束(适合需要同步结果的场景) - 用
t.detach()让线程后台运行(注意:线程内不能访问栈变量,否则悬垂) - 更安全的做法是把
std::thread包进 RAII 类,或用std::jthread(C++20),它自动在析构时join()
传参到线程函数要小心值拷贝和引用捕获
std::thread 构造时对参数默认做移动或拷贝,不会隐式转引用。想传引用?必须显式套 std::ref(x);想传右值但避免拷贝?用 std::move(x)。
典型翻车现场:std::thread t{[x]{ /* x 是副本 */ }}; 拉 lambda 进线程没问题,但若写成 std::thread t{func, &x};,编译失败——因为 &x 是左值,会被当作要拷贝的参数,而非传递引用。
立即学习“C++免费学习笔记(深入)”;
- 传原始指针或引用:用
std::ref(x)或std::cref(x) - 传大对象避免拷贝:用
std::move(obj)(确保原对象后续不再访问) - lambda 捕获引用(
[&x])在线程里用危险:外部变量生命周期可能已结束
共享数据不加锁就读写 = 未定义行为
两个线程同时写同一内存位置,或一写一读且无同步,C++ 标准直接定义为未定义行为(UB)。不是“大概率出错”,而是编译器可任意优化、CPU 可乱序执行、结果完全不可预测。
别信“我只读 int,应该原子吧”——即使 int 在平台是自然对齐且单指令读写,也不保证可见性与顺序性。用 std::atomic<int></int> 才算真正原子;普通变量必须配 std::mutex 或其他同步机制。
- 读写同一
std::vector元素?要互斥锁,std::vector本身不是线程安全的 - 只读多个线程共用的数据?只要确认期间没人写,可以不锁(但得确保初始化完成且发布正确)
- 用
std::atomic_flag做自旋锁?小心 CPU 空转耗尽资源,仅适合极短临界区
std::async 默认不立即启动,且 std::future 析构会阻塞
std::async(std::launch::deferred, ...) 是延迟执行,调用 get() 或 wait() 时才跑;而 std::launch::async 才真启新线程。但默认策略是两者都允许,编译器可任选——这意味着代码行为可能跨平台不一致。
更隐蔽的坑是:std::future 对象析构时,如果异步任务还没完成,会**阻塞等待结束**。这在局部变量、容器元素销毁、异常栈展开时非常容易触发意外阻塞。
- 明确指定启动策略:
std::async(std::launch::async, func) - 避免让
std::future靠作用域自动析构;及时调用get()或wait_for(...) - 需要多任务并行又不想阻塞?把
std::future存进容器,统一管理生命周期
线程安全不是加个锁就万事大吉,关键是厘清谁在什么时候访问什么数据。很多问题不在语法,而在状态发布的时机、对象生命周期的交叉、以及编译器对重排序的自由度。











