C++多线程实现_C++11/14/17多线程实现方案

11次阅读

std::Thread 构造即启动,必须在析构前调用 join() 或 detach(),否则触发 std::terminate();std::mutex 保护临界区,std::atomic 仅适用于单个内置类型的原子操作;std::async 适合有返回值的一次性并发计算,需注意 future 生命周期。

C++多线程实现_C++11/14/17多线程实现方案

标准库std::threadc++11 起最直接、最轻量的线程实现方式,无需第三方依赖,但必须手动管理生命周期和同步——漏掉 join()detach() 会导致程序崩溃或未定义行为。

如何正确启动和等待一个线程

std::thread 构造即启动,不能像 pthread 那样“创建后才运行”。构造后必须在析构前明确调用 join()(等待结束)或 detach()(分离执行),否则析构时会调用 std::terminate()

常见错误现象:std::thread::~thread() called on a joinable thread

  • 线程对象局部变量?确保在作用域结束前调用 join(),推荐用 RaiI 封装(如 scoped_thread 类)
  • 需要异步执行且不关心结果?用 detach(),但要注意:分离后无法再同步,线程内访问的局部变量可能已销毁
  • 传参时默认按值拷贝;若需引用,必须包装成 std::ref(x),否则编译失败或行为异常

怎样安全地共享数据:std::mutexstd::atomic 的分工

std::mutex 用于保护临界区(比如修改一个容器或结构体),而 std::atomic 仅适用于单个内置类型(int指针等)的读-改-写操作。二者不可混用:对 std::atomic 加锁是冗余的;对 std::vector 只用 std::atomic 是无效的。

立即学习C++免费学习笔记(深入)”;

使用场景:

  • 计数器累加?优先用 std::atomic,避免锁开销
  • std::map 插入元素?必须用 std::mutex 保护整个操作
  • 避免死锁?按固定顺序获取多个锁,或用 std::lock() + std::unique_lock 的延迟锁定

std::asyncstd::future 适合什么场景

std::async 本质是线程池的简化替代——它不保证一定新建线程(std::launch::deferred 模式下是惰性求值),也不提供任务取消机制。适合一次性、有返回值、逻辑清晰的并发计算。

容易踩的坑:

  • std::future 对象被移动后,原对象变为无效状态,再次调用 get() 会抛 std::future_error
  • 不显式保存 std::future?它会在作用域结束时阻塞等待完成,相当于隐式同步,失去并发意义
  • C++17 起 std::shared_future 支持多次 get(),但需注意其内部仍只计算一次

从 C++11 到 C++17 的关键演进点

不是所有特性都向后兼容:C++14 允许 std::thread 构造函数参数完美转发;C++17 引入 std::shared_mutex(读写锁)和 std::latch/std::barrier(更轻量的线程协调原语),但 MSVC 2017 和 GCC 7+ 才开始稳定支持。

性能与兼容性影响:

  • std::latch 比基于 std::condition_variable 的手写屏障更高效,但不可重用;std::barrier循环使用
  • std::shared_mutex 在读多写少场景下显著降低争用,但某些旧平台(如 macOS clang)仍依赖 boost::shared_mutex
  • 跨版本移植时,避免依赖 C++17 的 std::jthread(C++20 引入),它自动 join(),但目前主流项目仍以 C++11/14 为基线

真正难的从来不是“怎么开线程”,而是判断哪些数据要保护、哪些可以无锁、哪些该用更高层抽象(如 std::async),以及在不同标准版本间做取舍时,清楚知道某个 API 在目标编译器上是否真能用。

text=ZqhQzanResources