1. 现代C++多线程编程概述
十年前我刚接触多线程编程时,C++11标准还没发布,我们只能用pthread或者Windows API这些平台相关的库来写多线程程序。每次写代码都得加一堆#ifdef,调试起来简直是一场噩梦。直到2011年C++11标准发布,
现代C++多线程编程主要解决三个核心问题:如何创建和管理线程、如何安全地共享数据、如何协调线程间的执行顺序。这些问题在C++11之前都需要开发者自己解决,现在标准库提供了完整的工具链。
提示:现代C++指的是C++11及之后的版本,这些版本在多线程支持方面有质的飞跃
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线程创建与管理
2.1 线程基础操作
创建线程最简单的方式就是使用std::thread:
cpp复制#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!\n";
}
int main() {
std::thread t(hello);
t.join(); // 等待线程结束
return 0;
}
这里有几个关键点需要注意:
- 线程对象创建时就会立即执行传入的函数
- 必须明确决定是join()等待线程结束,还是detach()分离线程
- 如果既没join也没detach,程序终止时会调用std::terminate()
2.2 线程生命周期管理
在实际项目中,直接使用裸std::thread的情况其实不多,更常见的做法是用RAII包装:
cpp复制class ThreadGuard {
std::thread& t;
public:
explicit ThreadGuard(std::thread& t_) : t(t_) {}
~ThreadGuard() {
if(t.joinable()) {
t.join();
}
}
ThreadGuard(const ThreadGuard&)=delete;
ThreadGuard& operator=(const ThreadGuard&)=delete;
};
void use_thread() {
std::thread t([]{
// 线程任务
});
ThreadGuard g(t);
// 函数结束时自动join
}
这种模式可以避免忘记join导致的资源泄露问题,是更安全的做法。
3. 线程间数据共享
3.1 互斥量与锁
多线程编程最头疼的就是数据竞争问题。现代C++提供了多种互斥量类型:
- std::mutex:基本互斥量
- std::recursive_mutex:可重入互斥量
- std::timed_mutex:带超时的互斥量
- std::shared_mutex(C++17):读写锁
使用互斥量的正确姿势:
cpp复制std::mutex m;
int shared_data = 0;
void safe_increment() {
std::lock_guard<std::mutex> lock(m);
++shared_data;
}
注意:永远不要在持有锁的情况下调用未知代码,这可能导致死锁
3.2 原子操作
对于简单的数据类型,使用原子操作通常比互斥量更高效:
cpp复制#include <atomic>
std::atomic<int> counter(0);
void increment() {
counter.fetch_add(1, std::memory_order_relaxed);
}
原子操作的内存序是个复杂话题,一般情况使用默认的memory_order_seq_cst就行,性能敏感场景可以考虑更宽松的内存序。
4. 线程同步机制
4.1 条件变量
条件变量用于线程间的通知机制:
cpp复制std::mutex m;
std::condition_variable cv;
bool ready = false;
void worker() {
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, []{ return ready; });
// 工作代码
}
void master() {
{
std::lock_guard<std::mutex> lock(m);
ready = true;
}
cv.notify_all();
}
条件变量使用时有个经典问题叫"虚假唤醒",所以wait的第二个参数(判断条件)是必须的。
4.2 信号量(C++20)
C++20引入了std::counting_semaphore:
cpp复制#include <semaphore>
std::counting_semaphore<10> sem(0);
void worker() {
sem.acquire();
// 工作代码
}
void master() {
sem.release(); // 允许worker执行
}
信号量比条件变量更轻量,适合简单的资源计数场景。
5. 高级并发模式
5.1 异步任务
std::async可以方便地启动异步任务:
cpp复制#include <future>
int compute() {
// 复杂计算
return 42;
}
int main() {
auto fut = std::async(std::launch::async, compute);
// 做其他事情...
int result = fut.get(); // 获取结果
}
std::async的启动策略有两种:
- std::launch::async:立即异步执行
- std::launch::deferred:延迟到get()时执行
5.2 线程池实现
标准库没有直接提供线程池,但我们可以自己实现一个简单的版本:
cpp复制class ThreadPool {
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop = false;
public:
explicit ThreadPool(size_t threads) {
for(size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
while(true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
if(stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template<class F>
void enqueue(F&& f) {
{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(auto &worker : workers)
worker.join();
}
};
这个线程池实现包含了任务队列、工作线程管理和线程安全等核心要素。
6. 性能优化与调试
6.1 避免虚假共享
虚假共享(False Sharing)是多线程性能的隐形杀手:
cpp复制struct Data {
alignas(64) int a; // 64字节对齐
alignas(64) int b; // 确保不在同一缓存行
};
Data data;
void thread1() {
for(int i = 0; i < 1000000; ++i) ++data.a;
}
void thread2() {
for(int i = 0; i < 1000000; ++i) ++data.b;
}
通过alignas可以确保不同变量不在同一缓存行,避免CPU缓存频繁失效。
6.2 线程安全分析工具
TSAN(ThreadSanitizer)是检测数据竞争的利器:
bash复制clang++ -fsanitize=thread -g your_program.cpp
运行程序时,TSAN会报告所有的数据竞争情况。类似的还有Helgrind等工具。
7. 现代C++并发新特性
7.1 C++20的std::jthread
std::jthread是带有自动join功能的线程:
cpp复制void task() {
// 长时间运行的任务
}
int main() {
std::jthread t(task); // 析构时自动join
// 不需要手动调用join
return 0;
}
7.2 C++20的std::stop_token
提供了更优雅的线程停止机制:
cpp复制void worker(std::stop_token stoken) {
while(!stoken.stop_requested()) {
// 工作代码
}
}
int main() {
std::jthread t(worker);
// 需要停止时
t.request_stop();
}
8. 实战经验与陷阱
8.1 死锁预防
死锁的四个必要条件:
- 互斥条件
- 占有并等待
- 不可抢占
- 循环等待
避免死锁的实用技巧:
- 总是以固定顺序获取锁
- 使用std::lock同时获取多个锁
- 避免在持有锁时调用用户代码
- 使用RAII管理锁的生命周期
8.2 异常安全
多线程环境下的异常处理需要特别注意:
cpp复制void unsafe_op() {
std::mutex m;
m.lock();
some_function_that_may_throw(); // 如果抛出异常,锁永远不会释放
m.unlock();
}
void safe_op() {
std::mutex m;
std::lock_guard<std::mutex> lock(m); // 异常安全
some_function_that_may_throw();
}
9. 性能考量
9.1 线程数量选择
最佳线程数量通常与硬件相关:
cpp复制unsigned num_threads = std::thread::hardware_concurrency();
但实际情况更复杂,需要考虑:
- CPU核心数
- 任务类型(CPU密集型/IO密集型)
- 缓存利用率
- 操作系统调度开销
9.2 无锁编程
在极端性能场景下可能需要无锁数据结构:
cpp复制template<typename T>
class LockFreeQueue {
struct Node {
std::shared_ptr<T> data;
std::atomic<Node*> next;
Node(T const& data_) : data(std::make_shared<T>(data_)) {}
};
std::atomic<Node*> head;
std::atomic<Node*> tail;
public:
void push(T const& data) {
Node* new_node = new Node(data);
Node* old_tail = tail.load();
while(!old_tail->next.compare_exchange_weak(nullptr, new_node)) {
old_tail = tail.load();
}
tail.compare_exchange_weak(old_tail, new_node);
}
std::shared_ptr<T> pop() {
Node* old_head = head.load();
while(old_head && !head.compare_exchange_weak(old_head, old_head->next)) {
old_head = head.load();
}
return old_head ? old_head->data : std::shared_ptr<T>();
}
};
无锁编程非常复杂,除非确实需要,否则建议使用标准库提供的线程安全容器。
10. 实际项目中的应用模式
10.1 生产者-消费者模式
经典的多线程设计模式:
cpp复制template<typename T>
class ProducerConsumer {
std::queue<T> queue;
std::mutex m;
std::condition_variable cv;
bool done = false;
public:
void produce(T value) {
std::lock_guard<std::mutex> lock(m);
queue.push(std::move(value));
cv.notify_one();
}
bool consume(T& value) {
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [this]{ return !queue.empty() || done; });
if(queue.empty() && done) return false;
value = std::move(queue.front());
queue.pop();
return true;
}
void shutdown() {
{
std::lock_guard<std::mutex> lock(m);
done = true;
}
cv.notify_all();
}
};
10.2 Map-Reduce模式
并行计算常用模式:
cpp复制template<typename MapFunc, typename ReduceFunc>
auto map_reduce(const std::vector<int>& input, MapFunc map, ReduceFunc reduce, size_t num_threads) {
std::vector<std::thread> threads;
std::vector<int> results(num_threads);
size_t chunk_size = input.size() / num_threads;
for(size_t i = 0; i < num_threads; ++i) {
threads.emplace_back([i, chunk_size, &input, &results, &map] {
size_t start = i * chunk_size;
size_t end = (i == num_threads - 1) ? input.size() : start + chunk_size;
int result = 0;
for(size_t j = start; j < end; ++j) {
result += map(input[j]);
}
results[i] = result;
});
}
for(auto& t : threads) t.join();
return std::accumulate(results.begin(), results.end(), 0, reduce);
}
11. C++内存模型深入
11.1 内存序详解
C++原子操作支持多种内存序:
- memory_order_relaxed:没有同步约束
- memory_order_consume:依赖关系同步
- memory_order_acquire:获取操作
- memory_order_release:释放操作
- memory_order_acq_rel:获取-释放操作
- memory_order_seq_cst:顺序一致性(默认)
正确选择内存序可以显著提升性能:
cpp复制std::atomic<bool> ready(false);
int data = 0;
void producer() {
data = 42; // 1
ready.store(true, std::memory_order_release); // 2
}
void consumer() {
while(!ready.load(std::memory_order_acquire)); // 3
assert(data == 42); // 4
}
在这个例子中,release-acquire配对确保了1 happens-before 4。
11.2 栅栏操作
内存栅栏提供了更底层的控制:
cpp复制std::atomic<bool> x, y;
std::atomic<int> z;
void write_x_then_y() {
x.store(true, std::memory_order_relaxed); // 1
std::atomic_thread_fence(std::memory_order_release); // 2
y.store(true, std::memory_order_relaxed); // 3
}
void read_y_then_x() {
while(!y.load(std::memory_order_relaxed)); // 4
std::atomic_thread_fence(std::memory_order_acquire); // 5
if(x.load(std::memory_order_relaxed)) // 6
++z;
}
这里的栅栏确保了1 happens-before 6。
12. 并行算法(C++17)
C++17引入了并行版STL算法:
cpp复制#include <execution>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> v(1000000);
// 并行排序
std::sort(std::execution::par, v.begin(), v.end());
// 并行变换
std::transform(std::execution::par,
v.begin(), v.end(), v.begin(),
[](int x) { return x * 2; });
// 并行累加
int sum = std::reduce(std::execution::par, v.begin(), v.end());
}
可用的执行策略:
- seq:顺序执行
- par:并行执行
- par_unseq:并行+向量化
13. 协程与异步编程(C++20)
C++20引入了协程支持:
cpp复制#include <coroutine>
#include <iostream>
struct ReturnObject {
struct promise_type {
ReturnObject get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
ReturnObject coro() {
std::cout << "Hello from coroutine\n";
co_return;
}
int main() {
coro();
std::cout << "Back in main\n";
}
协程特别适合异步IO场景,可以写出同步风格的异步代码。
14. 调试多线程程序
14.1 常见问题诊断
多线程程序常见问题:
- 数据竞争:使用TSAN检测
- 死锁:使用死锁检测工具或日志分析
- 活锁:线程持续工作但无法推进
- 资源耗尽:线程太多或锁争用严重
14.2 日志技巧
有效的多线程日志策略:
- 每个线程有独立ID
- 关键操作前后记录状态
- 使用原子操作记录计数器
- 避免日志本身成为性能瓶颈
cpp复制class ThreadLogger {
static std::atomic<int> counter;
thread_local static int thread_id;
public:
ThreadLogger() {
if(thread_id == 0) {
thread_id = ++counter;
}
}
void log(const std::string& msg) {
std::lock_guard<std::mutex> lock(log_mutex);
std::cout << "Thread " << thread_id << ": " << msg << std::endl;
}
};
15. 跨平台注意事项
不同平台线程实现的差异:
- 线程栈大小:Windows默认1MB,Linux默认8MB
- 线程优先级:不同平台API不同
- 线程本地存储:语法略有差异
- 信号处理:Unix信号与线程交互复杂
可移植代码建议:
- 使用标准库设施
- 避免平台特定API
- 测试不同平台的线程数量限制
- 注意不同平台的默认栈大小
16. 线程设计模式
16.1 Active Object模式
将方法调用与方法执行解耦:
cpp复制class ActiveObject {
std::queue<std::function<void()>> tasks;
std::mutex m;
std::condition_variable cv;
std::thread worker;
bool done = false;
void run() {
while(true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [this]{ return !tasks.empty() || done; });
if(done && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
}
public:
ActiveObject() : worker(&ActiveObject::run, this) {}
~ActiveObject() {
{
std::lock_guard<std::mutex> lock(m);
done = true;
}
cv.notify_all();
worker.join();
}
template<typename F>
void execute(F f) {
{
std::lock_guard<std::mutex> lock(m);
tasks.push(std::function<void()>(f));
}
cv.notify_one();
}
};
16.2 Monitor Object模式
封装对象使其线程安全:
cpp复制template<typename T>
class Monitor {
mutable T object;
mutable std::mutex m;
public:
template<typename F>
auto operator()(F f) const -> decltype(f(object)) {
std::lock_guard<std::mutex> lock(m);
return f(object);
}
};
class BankAccount {
int balance = 0;
public:
void deposit(int amount) { balance += amount; }
void withdraw(int amount) { balance -= amount; }
int getBalance() const { return balance; }
};
int main() {
Monitor<BankAccount> account;
account([](BankAccount& a) {
a.deposit(100);
});
int balance = account([](const BankAccount& a) {
return a.getBalance();
});
}
17. 性能优化实战
17.1 锁粒度优化
错误的粗粒度锁:
cpp复制class BadCache {
std::map<int, int> cache;
std::mutex m;
public:
int get(int key) {
std::lock_guard<std::mutex> lock(m);
return cache[key];
}
void set(int key, int value) {
std::lock_guard<std::mutex> lock(m);
cache[key] = value;
}
};
改进的细粒度锁:
cpp复制class BetterCache {
struct Bucket {
std::mutex m;
std::map<int, int> data;
};
std::vector<Bucket> buckets;
Bucket& get_bucket(int key) {
return buckets[key % buckets.size()];
}
public:
BetterCache(size_t bucket_count = 19) : buckets(bucket_count) {}
int get(int key) {
auto& bucket = get_bucket(key);
std::lock_guard<std::mutex> lock(bucket.m);
return bucket.data[key];
}
void set(int key, int value) {
auto& bucket = get_bucket(key);
std::lock_guard<std::mutex> lock(bucket.m);
bucket.data[key] = value;
}
};
17.2 无锁队列优化
基于环形缓冲区的无锁队列:
cpp复制template<typename T, size_t Capacity>
class LockFreeRingBuffer {
std::array<T, Capacity> buffer;
alignas(64) std::atomic<size_t> head{0};
alignas(64) std::atomic<size_t> tail{0};
public:
bool push(const T& item) {
size_t current_tail = tail.load(std::memory_order_relaxed);
size_t next_tail = (current_tail + 1) % Capacity;
if(next_tail == head.load(std::memory_order_acquire)) {
return false; // 队列满
}
buffer[current_tail] = item;
tail.store(next_tail, std::memory_order_release);
return true;
}
bool pop(T& item) {
size_t current_head = head.load(std::memory_order_relaxed);
if(current_head == tail.load(std::memory_order_acquire)) {
return false; // 队列空
}
item = buffer[current_head];
head.store((current_head + 1) % Capacity, std::memory_order_release);
return true;
}
};
18. 现代C++并发最佳实践
- 优先使用标准库设施而非平台特定API
- 使用RAII管理资源(线程、锁等)
- 最小化共享数据,最大化不可变数据
- 使用高级抽象(如async、future)而非低级线程操作
- 避免裸锁,使用标准库提供的线程安全容器
- 注意异常安全,特别是在持有锁时
- 使用工具(TSAN、Helgrind)检测数据竞争和死锁
- 性能敏感场景考虑无锁编程,但需充分测试
- 合理设置线程优先级和亲和性(平台相关)
- 文档记录线程安全和并发假设
19. 未来发展方向
C++23及以后版本可能会引入:
- 更强大的执行器(executor)支持
- 改进的协程支持
- 标准线程池实现
- 更丰富的并行算法
- 增强的内存模型支持
20. 推荐学习资源
- 《C++ Concurrency in Action》Anthony Williams
- 《The Art of Multiprocessor Programming》Herlihy & Shavit
- C++标准文档并发相关章节
- Intel Threading Building Blocks文档
- Microsoft PPL库文档
