std::thread 构造后必须 detach 或 join,否则析构时调用 std::terminate() 导致进程崩溃;std::mutex 不可拷贝或移动,需引用捕获;多资源加锁应使用 std::lock() 避免死锁;读多写少场景优先用 std::shared_mutex;condition_variable 等待必须配合 while 循环和谓词。

std::thread 构造后必须 detach 或 join,否则程序崩溃
构造 std::thread 对象后,如果它仍处于“可加入”(joinable)状态,而析构函数被调用(比如线程对象是局部变量,作用域结束),会直接调用 std::terminate() —— 不抛异常,不打印日志,进程立刻退出。这是新手踩得最多、最隐蔽的坑。
- 必须在对象生命周期结束前显式调用
join()(等待线程结束)或detach()(移交后台运行) -
join()只能调用一次;重复调用或对已detach()的线程调用,行为未定义 - 别在析构函数里写
join()—— 如果线程还在跑,析构卡住;如果线程已结束但没及时join(),析构时就崩 - 推荐封装:用 RAII 类(如
scoped_thread)自动join(),或明确设计为detach()场景(如日志上报、心跳发送)
std::mutex 不能拷贝,也不能跨线程传递所有权
std::mutex 是非复制、非移动类型,所有成员函数(包括构造、赋值、拷贝)都被删除。试图把它塞进 std::vector、传给 std::thread 的 lambda 捕获列表(按值捕获)、或者返回一个临时 mutex,编译直接报错:use of deleted function。
- 锁对象通常作为类成员或全局/静态变量存在,生命周期需覆盖所有可能访问它的线程
- lambda 中需要访问锁时,务必用引用捕获:
[&mtx]{ mtx.lock(); ... },而不是[mtx]{...} - 多个资源需加锁时,统一用
std::lock()+std::unique_lock避免死锁,不要手写lock()/unlock()顺序 -
std::recursive_mutex仅当真需要同一线程多次加同一把锁时才用——多数情况说明设计有问题
std::shared_mutex 在读多写少场景下比 std::mutex 更高效
如果你的临界区主要是并发读、极少写(比如配置缓存、路由表、白名单),std::shared_mutex(C++17 起)允许任意数量线程同时读,但写操作独占。相比所有读也排队的 std::mutex,吞吐明显提升。
- 读操作用
std::shared_lock<:shared_mutex></:shared_mutex>(RAII,自动 unlock_shared) - 写操作用
std::unique_lock<:shared_mutex></:shared_mutex>(等价于普通std::unique_lock) - 注意:Windows 上 VS2019+ 才完整支持;GCC 8+、Clang 7+ 支持较好;老标准库(如 libstdc++ 7)可能只提供
std::shared_timed_mutex,性能略差 - 别为了“看起来高级”强行替换——写操作频繁时,
shared_mutex开销反而更大(内部状态更复杂)
std::condition_variable 等待必须配合 while 循环和 predicate
直接用 cv.wait(lock) 不带谓词,几乎总是错的。虚假唤醒(spurious wakeup)是 POSIX 和 C++ 标准明确允许的行为,线程可能在没人 notify 的情况下醒来,此时共享状态未必满足预期。
立即学习“C++免费学习笔记(深入)”;
- 永远用
cv.wait(lock, [&]{ return condition; });形式,让 wait 内部自动重检条件 - 条件变量本身不保护数据——
condition表达式中访问的所有变量,必须由同一把锁保护(通常是同一个std::mutex) -
notify_one()只唤醒一个等待线程;notify_all()唤醒全部——后者在竞争激烈时可能引发“惊群”,但比漏唤醒安全;选哪个取决于业务语义 - 别在持有锁时做耗时操作(比如 I/O、复杂计算),否则阻塞其他线程;wait 唤醒后应尽快检查条件并释放锁
线程同步真正难的不是语法,而是状态建模:哪些变量会被谁改、在什么时机可见、失败路径是否破坏不变量。一个 std::atomic_flag 和三行内存序注释,有时比五层锁嵌套更可靠。










