在当今多核处理器普及的时代,充分利用硬件并发能力已成为高性能编程的关键。C++11引入的现代并发编程支持使得开发者能够以标准化、可移植的方式编写多线程程序。本文将全面介绍C++并发编程的各个方面,从基础概念到实际应用,帮助您掌握这一重要技能。
一、并发编程概述
1.1 为什么需要并发编程
随着处理器核心数量的增加,单线程程序已经无法充分利用现代硬件资源。并发编程主要解决以下问题:
-
性能提升:通过并行处理加速计算密集型任务
-
响应性增强:保持用户界面响应同时执行后台操作
-
资源利用:高效利用多核CPU和I/O等待时间
-
任务分离:将复杂系统分解为独立协作的组件
1.2 并发与并行的区别
虽然经常混用,但这两个概念有本质区别:
-
并发(Concurrency):多个任务交替执行,看似同时进行
-
并行(Parallelism):多个任务真正同时执行,需要多核支持
C++标准库提供了同时支持这两种模式的统一接口。
二、线程基础
2.1 线程创建与管理
C++通过std::thread
类提供线程支持,基本用法如下:
#include <iostream>
#include <thread>void thread_task() {std::cout << "Hello from thread " << std::this_thread::get_id() << "\n";
}int main() {std::thread t(thread_task);std::cout << "Main thread ID: " << std::this_thread::get_id() << "\n";t.join();return 0;
}
关键点:
-
线程在构造时立即开始执行
-
必须明确选择
join()
或detach()
-
主线程结束时会终止整个程序
2.2 线程参数传递
线程函数支持任意数量和类型的参数:
void print_sum(int a, int b) {std::cout << a + b << "\n";
}int main() {std::thread t(print_sum, 3, 4);t.join();
}
参数总是按值传递,如需传递引用必须使用std::ref
包装:
void modify(int& x) {x *= 2;
}int main() {int value = 5;std::thread t(modify, std::ref(value));t.join();std::cout << value; // 输出10
}
2.3 线程生命周期管理
正确处理线程生命周期至关重要:
std::thread create_thread() {return std::thread([]{std::cout << "New thread\n";});
}int main() {std::thread t = create_thread();if (t.joinable()) {t.detach(); // 或t.join()}// 错误示例:线程未join或detach// std::thread t2([]{...});// t2的析构函数会调用std::terminate
}
三、同步机制
3.1 互斥量(Mutex)
互斥量是最基本的同步原语,C++提供多种变体:
类型 | 特性 |
---|---|
std::mutex | 基本互斥量 |
std::recursive_mutex | 可重入互斥量 |
std::timed_mutex | 支持超时锁定 |
std::shared_mutex | 读写锁(C++14) |
基本用法:
std::mutex mtx;
int shared_data = 0;void increment() {mtx.lock();++shared_data;mtx.unlock();
}// 更安全的RAII方式
void safe_increment() {std::lock_guard<std::mutex> lock(mtx);++shared_data;
}
3.2 条件变量(Condition Variable)
条件变量允许线程等待特定条件成立:
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
std::string data;void worker() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; });std::cout << "Processing: " << data << "\n";
}int main() {std::thread t(worker);// 准备数据{std::lock_guard<std::mutex> lock(mtx);data = "Sample data";ready = true;}cv.notify_one();t.join();
}
3.3 原子操作
对于简单数据类型,原子操作比互斥量更高效:
#include <atomic>std::atomic<int> counter(0);void increment_atomic() {for (int i = 0; i < 1000; ++i) {++counter;}
}int main() {std::thread t1(increment_atomic);std::thread t2(increment_atomic);t1.join();t2.join();std::cout << counter; // 总是2000
}
四、高级并发特性
4.1 异步任务(std::async)
std::async
提供更高层次的异步执行抽象:
#include <future>int compute() {std::this_thread::sleep_for(std::chrono::seconds(1));return 42;
}int main() {auto future = std::async(std::launch::async, compute);// 执行其他工作...std::cout << "Result: " << future.get() << "\n";
}
启动策略:
-
std::launch::async
:立即异步执行 -
std::launch::deferred
:延迟到get()/wait()时执行
4.2 并行算法(C++17)
C++17引入了并行标准算法:
#include <algorithm>
#include <vector>int main() {std::vector<int> data(1000000);// 并行排序std::sort(std::execution::par, data.begin(), data.end());// 并行变换std::transform(std::execution::par,data.begin(), data.end(), data.begin(),[](int x) { return x * 2; });
}
执行策略:
-
std::execution::seq
:顺序执行 -
std::execution::par
:并行执行 -
std::execution::par_unseq
:并行且向量化
五、并发设计模式
5.1 生产者-消费者模式
#include <queue>std::mutex mtx;
std::condition_variable cv;
std::queue<int> msg_queue;void producer() {for (int i = 0; i < 10; ++i) {{std::lock_guard<std::mutex> lock(mtx);msg_queue.push(i);}cv.notify_one();std::this_thread::sleep_for(std::chrono::milliseconds(100));}
}void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return !msg_queue.empty(); });int msg = msg_queue.front();msg_queue.pop();lock.unlock();if (msg == -1) break; // 终止信号std::cout << "Received: " << msg << "\n";}
}
5.2 线程池实现
基本线程池结构:
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: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 (std::thread &worker : workers)worker.join();}
};
六、性能考量与最佳实践
-
避免虚假共享:将频繁写入的变量放在不同缓存行
struct alignas(64) CacheLineAligned {int data; };
-
锁粒度:保持锁的粒度尽可能小
-
无锁编程:在性能关键路径考虑无锁数据结构
-
线程数量:通常等于硬件线程数,可通过
std::thread::hardware_concurrency()
获取 -
异常安全:确保锁在异常情况下也能释放
-
死锁预防:
-
按固定顺序获取多个锁
-
使用
std::lock
同时获取多个锁 -
避免在持有锁时调用用户代码
-
七、C++20新增特性
-
信号量(Semaphore):
#include <semaphore> std::counting_semaphore<10> sem(0);void worker() {sem.acquire();// 执行工作... }void post() {sem.release(); }
-
闩(Latch)和屏障(Barrier):
std::latch completion_latch(5); // 需要5次count_down() std::barrier sync_point(3); // 每3个线程到达后继续
-
协程支持:为异步编程提供新范式
结语
C++并发编程是一个庞大而复杂的主题,本文涵盖了从基础线程操作到高级设计模式的广泛内容。掌握这些知识后,您将能够:
-
构建高效的多线程应用程序
-
设计线程安全的数据结构
-
避免常见的并发陷阱
-
充分利用现代硬件资源
记住,并发编程的首要目标是正确性,其次才是性能。始终优先考虑代码的安全性和可维护性,只有在必要时才进行复杂的优化。
随着C++标准的演进,并发支持仍在不断增强。建议持续关注新特性,如C++23中预计加入的更多并行算法和增强的协程支持。