1. 为什么C++多线程编程需要最佳实践?
我第一次接触多线程编程是在一个实时数据处理项目中,当时因为线程同步问题导致数据错乱,整整排查了两天才找到原因。C++作为系统级编程语言,其多线程能力既强大又危险——就像一把没有护手的利剑,用得好可以所向披靡,用不好容易伤到自己。
现代CPU早已进入多核时代,单线程程序无法充分利用硬件资源。根据我的实测数据,一个4核8线程的处理器,合理使用多线程可以将计算密集型任务的执行时间缩短到单线程的1/5甚至更低。但多线程带来的并发问题也让不少开发者望而生畏:数据竞争、死锁、虚假唤醒...这些bug往往难以复现,却可能在线上环境造成灾难性后果。
2. 多线程基础与核心概念
2.1 线程与进程的本质区别
很多初学者容易混淆线程和进程的概念。简单来说,进程是资源分配的基本单位,而线程是CPU调度的基本单位。在我的项目经验中,一个进程至少包含一个主线程,可以创建多个子线程。这些线程共享进程的内存空间(包括堆、全局变量等),但各自拥有独立的栈空间和寄存器状态。
特别注意:共享内存既是优势也是风险源。我曾遇到过一个案例:两个线程同时修改同一个vector导致迭代器失效,程序随机崩溃。
2.2 C++标准库中的线程支持
C++11之前,我们只能使用平台特定的API(如pthread或Windows线程API)。现在标准库提供了完整的线程支持:
cpp复制#include <thread>
#include <iostream>
void worker(int id) {
std::cout << "Thread " << id << " is working\n";
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
return 0;
}
这段简单代码展示了最基本的线程创建和等待。但实际项目中,我们很少直接使用裸线程,而是需要更高级的同步机制。
3. 线程同步的四种武器
3.1 互斥锁(mutex)的正确使用姿势
互斥锁是最基础的同步工具,但使用不当会导致性能问题甚至死锁。标准库提供了多种mutex类型:
- std::mutex:基本互斥锁
- std::recursive_mutex:可重入锁
- std::timed_mutex:带超时的锁
- std::shared_mutex:读写锁(C++17)
我强烈推荐使用RAII风格的std::lock_guard和std::unique_lock:
cpp复制std::mutex mtx;
int shared_data = 0;
void safe_increment() {
std::lock_guard<std::mutex> lock(mtx);
++shared_data;
}
踩坑记录:我曾在一个高频交易系统中过度使用mutex,导致性能下降60%。后来通过减小临界区范围和改用读写锁优化。
3.2 条件变量的精妙之处
条件变量(condition_variable)用于线程间的通知机制,常与mutex配合使用。最常见的模式是生产者-消费者:
cpp复制std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
// 处理数据
}
void producer() {
{
std::lock_guard<std::m
