在 C++17 及以后,标准库提供了强大的并发工具,包括 std::thread、std::async、std::future、std::promise、std::mutex、std::shared_mutex、std::atomic 等。为了在多核时代写出安全、高效、易维护的代码,开发者需要掌握以下关键概念和实践。
-
理解并发与并行
- 并发是指程序在同一时刻可以交错执行多个任务;并行是指同时在多核上执行任务。C++ 标准库的线程工具同时支持这两种概念。
-
优先使用高层抽象
std::async与std::future:适用于需要立即得到异步结果的场景。它们隐藏了线程管理细节,减少资源泄漏风险。std::thread:直接管理线程生命周期,适合需要手动同步或自定义线程属性的情况。
-
显式管理线程生命周期
- detach 与 join:
detach()会让线程独立运行,程序结束时仍可能未完成;join()必须等待线程结束。通常推荐使用join(),或者在 RAII 包装类中自动 join。 - 资源泄漏风险:忘记
join()会导致程序异常终止。
- detach 与 join:
-
避免数据竞争
- 不可变共享:将共享数据设为
const或使用std::shared_ptr<const T>。 - 原子操作:
std::atomic适用于简单数据类型,提供无锁并发。 - 互斥锁:
std::mutex、std::shared_mutex(读写锁)可保护复杂对象。
- 不可变共享:将共享数据设为
-
使用
std::shared_mutex进行读写分离- 对读多写少的场景,使用
std::shared_lock(共享锁)进行读操作,使用std::unique_lock(独占锁)进行写操作,可显著提升并发度。
- 对读多写少的场景,使用
-
细粒度锁和锁分离
- 避免在同一锁下处理所有资源。对不同数据块使用不同锁,减少互斥冲突。
-
避免死锁
- 统一锁的获取顺序。
- 使用
std::lock同时锁定多个互斥量,避免手动顺序导致死锁。 - 尽量缩短临界区,减少持锁时间。
-
使用
std::condition_variable进行事件同步- 通过条件变量实现生产者/消费者模型。注意使用
std::unique_lock作为参数,并在等待前检查条件。
- 通过条件变量实现生产者/消费者模型。注意使用
-
利用
std::future与std::promise传递结果promise负责设置结果,future负责获取。通过future::get()自动等待,防止竞争。
-
避免过度使用
std::asyncstd::async的执行策略(launch::asyncvslaunch::deferred)不易控制。对于高并发场景,建议使用线程池。
-
线程池实现
- C++20 引入
std::jthread,支持自动停止。 - 自己实现线程池时,使用
std::queue+std::condition_variable管理任务。
- C++20 引入
-
性能测试与调优
- 使用
std::chrono计时,测量任务执行时间。 - `std::atomic ` 的原子操作通常比 `mutex` 更快,但只能处理基本类型。
- 通过
std::lock_guard替代手动 lock/unlock,减少错误。
- 使用
-
工具与检测
- ThreadSanitizer(TSan):检测数据竞争。
- Valgrind Helgrind:调试并发问题。
- Intel VTune:分析多线程性能。
-
示例代码:生产者消费者
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
class ThreadSafeQueue {
public:
void push(int value) {
std::lock_guard<std::mutex> lock(m_);
q_.push(value);
cv_.notify_one();
}
int pop() {
std::unique_lock<std::mutex> lock(m_);
cv_.wait(lock, [&]{ return !q_.empty(); });
int val = q_.front();
q_.pop();
return val;
}
private:
std::queue <int> q_;
std::mutex m_;
std::condition_variable cv_;
};
int main() {
ThreadSafeQueue q;
std::vector<std::thread> workers;
// Producer
workers.emplace_back([&]{
for (int i = 0; i < 10; ++i) q.push(i);
});
// Consumer
workers.emplace_back([&]{
for (int i = 0; i < 10; ++i)
std::cout << "Consumed: " << q.pop() << '\n';
});
for (auto& t : workers) t.join();
}
- 总结
- 优先使用标准库提供的并发抽象,避免手写低层同步代码。
- 显式管理线程,遵循 RAII,防止资源泄漏。
- 细粒度锁、读写分离 与 无锁原子 是提升并发性能的关键。
- 使用检测工具及性能分析来定位瓶颈。
遵循这些最佳实践,你将能在 C++ 项目中实现安全、高效且易维护的并发代码。