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 的组合