C++怎么使用thread C++线程同步与互斥锁【深度】

3次阅读

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

C++怎么使用thread C++线程同步与互斥锁【深度】

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::threadLambda 捕获列表(按值捕获)、或者返回一个临时 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_mutexc++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 和三行内存序注释,有时比五层锁嵌套更可靠。

text=ZqhQzanResources