1. C++并发编程概述
在当今多核处理器普及的时代,掌握并发编程已成为C++开发者的必备技能。我仍然记得第一次尝试用多线程加速图像处理程序时,由于对竞态条件理解不足导致程序随机崩溃的场景。C++11标准引入的线程库彻底改变了C++并发编程的格局,使得编写跨平台的多线程程序不再需要依赖平台特定的API。
并发编程的核心目标是充分利用现代CPU的多核能力,通过任务分解和并行执行来提高程序性能。与传统的单线程程序相比,并发程序能够更好地响应用户交互(如GUI应用),更高效地处理I/O密集型任务(如网络服务器),以及更快速地完成计算密集型工作(如科学计算)。
注意:并发(concurrency)并不等同于并行(parallelism)。并发是指多个任务在重叠的时间段内执行,而并行则是指多个任务真正同时执行。单核CPU上可以通过时间片轮转实现并发,但只有多核CPU才能实现真正的并行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线程基础与生命周期管理
2.1 线程创建与启动
C++11中创建线程非常简单,只需要包含
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()分离线程
- 线程函数可以接受任意可调用对象(函数指针、lambda、函数对象等)
2.2 线程生命周期管理
线程的生命周期管理是并发编程中最容易出错的地方之一。以下是一个典型的问题场景:
cpp复制void risky_function() {
std::thread t([]{
std::cout << "Running in background...\n";
});
// 忘记join或detach
} // 线程对象析构时,若仍可joinable,程序将终止!
正确处理线程生命周期的原则:
- 在std::thread对象销毁前,必须确保线程已经join或detach
- 对于需要长期运行的后台线程,通常选择detach
- 对于需要等待结果的线程,必须使用join
3. 共享数据与同步机制
3.1 竞态条件与数据竞争
当多个线程同时访问共享数据且至少有一个线程在修改数据时,就会发生数据竞争。考虑这个经典例子:
cpp复制int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
++counter;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << counter; // 结果通常小于200000
}
这个程序几乎永远不会输出预期的200000,因为++counter操作不是原子的,可能被线程切换打断。
3.2 互斥锁(std::mutex)
解决数据竞争的最基本方法是使用互斥锁:
cpp复制std::mutex mtx;
int counter = 0;
void safe_increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock();
++counter;
mtx.unlock();
}
}
更安全的做法是使用RAII风格的std::lock_guard:
cpp复制void safer_increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
} // 自动释放锁
}
3.3 条件变量(std::condition_variable)
条件变量用于线程间的通知机制,典型的生产者-消费者模式实现:
cpp复制std::mutex mtx;
std::condition_variable cv;
bool ready = false;
std::queue<int> data_queue;
void producer() {
for (
