C++ 并发与多线程入门
现代应用程序需要最大性能。C++ 中的并发与多线程能够有效利用多核处理器,加速任务执行。不理解这些概念,就无法创建高负载系统、数据处理程序或游戏引擎。
在本文中,我们将剖析 C++ 多线程的关键机制,从基础的 std::thread 到高级模式。您将学习如何避免数据竞争、正确同步线程以及编写安全的并行代码。
多线程基础:std::thread 和 std::jthread
std::thread 类 (C++11) 是多线程编程的基础。它允许在一个单独的线程中启动一个函数或可调用对象。来看一个最简单的例子:
#include <iostream>#include <thread>
void worker(int id) { std::cout << "线程 " << id << " 开始工作"; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "线程 " << id << " 完成";}
int main() { std::thread t1(worker, 1); std::thread t2(worker, 2); t1.join(); t2.join(); return 0;}关键点:
- join() — 主线程等待子线程完成
- detach() — 线程独立继续运行(存在资源泄漏风险)
- std::jthread (C++20) — 在析构函数中自动调用 join()
在调用 join() 之前始终检查 joinable(),以避免重复连接。
线程同步:互斥锁与锁
多线程的主要问题是数据竞争,即多个线程同时修改共享变量。使用互斥锁进行保护。
std::mutex 和 std::lock_guard
#include <iostream>#include <thread>#include <mutex>
std::mutex mtx;int counter = 0;
void increment() { for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); ++counter; }}
int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "结果: " << counter << ""; // 200000 return 0;}std::lock_guard 在离开作用域时自动释放互斥锁。如需更灵活的控制,请使用 std::unique_lock。
死锁与 std::scoped_lock
当两个线程互相等待时,就会发生死锁。解决方案是使用 std::scoped_lock (C++17),它可以原子性地锁定多个互斥锁:
std::mutex m1, m2;
void safe_function() { std::scoped_lock lock(m1, m2); // 安全操作两个资源}并行算法与 std::async
如需执行异步任务而无需手动管理线程,请使用 std::async。它返回一个 std::future,从中可以获取结果。
#include <iostream>#include <future>#include <chrono>
int compute(int n) { std::this_thread::sleep_for(std::chrono::seconds(n)); return n * n;}
int main() { auto future1 = std::async(std::launch::async, compute, 2); auto future2 = std::async(std::launch::async, compute, 3); std::cout << "结果: " << future1.get() << ", " << future2.get() << ""; // 4, 9 return 0;}启动策略:
std::launch::async— 在新线程中启动std::launch::deferred— 延迟执行(在调用 get() 时同步执行)