C++ 并发与多线程:进阶篇

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C++ 中的并发与多线程:从基础到高级技术



现代应用程序越来越需要最高性能,尤其是在处理多核处理器时。C++ 从 C++11 到最新标准提供了强大的并发与多线程工具。在本文中,我们将深入探讨多线程的高级方面:从正确使用互斥锁和原子操作到复杂的同步模式。本材料面向已经熟悉 C++ 线程基础的开发者。



1. 线程管理:std::thread 和 std::jthread



std::thread 类是 C++ 多线程编程的基础。然而,在高级层面,不仅要创建线程,还要正确管理其生命周期。从 C++20 开始,出现了 std::jthread,它在销毁时自动加入,防止资源泄漏。



使用 std::jthread 的示例:



#include <iostream>#include <thread>#include <chrono>

void worker(int id) { std::this_thread::sleep_for(std::chrono::milliseconds(100 * id)); std::cout << "线程 " << id << " 已完成";}

int main() { // std::jthread 在离开作用域时自动加入 std::jthread t1(worker, 1); std::jthread t2(worker, 2); std::jthread t3(worker, 3); // 无需显式调用 join() 或 detach() // 当 t1、t2、t3 销毁时,main 等待它们完成 std::cout << "主线程继续工作"; return 0;}


重要提示:std::jthread 还通过 std::stop_token 支持停止机制,从而可以安全地终止长时间运行的后台任务。



2. 访问同步:互斥锁、锁和条件变量



多线程的主要问题是数据竞争。为了防止数据竞争,使用互斥锁。在高级层面,建议使用 std::scoped_lock(C++17)来捕获多个互斥锁,而不会出现死锁风险。



使用 std::scoped_lock 的示例:



#include <iostream>#include <mutex>#include <thread>#include <vector>

std::mutex mtx1, mtx2;int shared_data = 0;

void safe_increment(int id) { // scoped_lock 按顺序捕获两个互斥锁,避免死锁 std::scoped_lock lock(mtx1, mtx2); ++shared_data; std::cout << "线程 " << id << " 将值增加到 " << shared_data << "";}

int main() { std::vector<std::jthread> threads; for (int i = 0; i < 10; ++i) { threads.emplace_back(safe_increment, i); } return 0;}


对于更复杂的同步场景(例如生产者-消费者),使用条件变量(std::condition_variable)。它们允许线程等待特定事件的发生,而不会浪费资源进行忙等待。



3. 原子操作和 C++ 内存模型



原子操作(std::atomic)是无锁编程的基础。它们保证变量的读取和写入作为单个不可分割的操作执行。然而,在高级层面,理解 C++ 内存模型和内存序级别非常重要。



主要的内存序级别:

  • memory_order_relaxed — 最小保证,仅原子性
  • memory_order_acquire — 保证所有后续读取和写入将看到先前操作的结果
  • memory_order_release — 保证所有先前操作在此写入后可见
  • memory_order_acq_rel — acquire 和 release 的组合

博客

书籍推荐