1. 为什么需要多线程想象一下你正在厨房做饭一边煮汤一边切菜同时还要盯着烤箱里的面包。如果只能按顺序操作等汤煮好再切菜最后烤面包这顿饭恐怕要等到半夜才能吃上。多线程编程也是同样的道理——它让程序能够一心多用。在C中多线程可以带来三大优势性能提升现代CPU都是多核设计单线程程序只能用到其中一个核心。通过多线程我们可以把计算任务分配到多个核心上并行执行。实测一个图像处理程序使用4线程比单线程速度快了3.2倍响应更快GUI程序用单独线程处理用户交互避免主线程被耗时操作阻塞资源利用当一个线程等待I/O时其他线程可以继续使用CPU不过多线程也带来了新挑战比如我去年调试一个数据竞争问题时花了整整两天才发现是两个线程同时修改了同一个变量。接下来我们就从基础开始逐步掌握多线程的正确打开方式。2. 线程创建与管理2.1 创建第一个线程C11引入了thread头文件让线程创建变得非常简单。看这个例子#include iostream #include thread void downloadFile(std::string filename) { std::cout 开始下载 filename std::endl; // 模拟耗时操作 std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout filename 下载完成 std::endl; } int main() { std::thread t1(downloadFile, A.zip); std::cout 主线程继续运行... std::endl; t1.join(); // 等待t1完成 return 0; }这里有几个关键点线程对象t1在构造时立即开始执行参数通过值传递如果要传引用需要用std::refjoin()会阻塞当前线程直到目标线程完成2.2 线程的生死抉择每个线程创建后都面临两个选择join()等待线程结束确保资源正确回收detach()分离线程让它独立运行我曾经犯过一个错误在没调用join或detach的情况下直接让线程对象析构导致程序崩溃。正确的做法是std::thread t(task); if(t.joinable()) { // 必须检查 t.detach(); // 或 t.join(); }实际项目中我更推荐使用RAII风格的线程管理类确保线程不会意外泄露3. 线程同步的艺术3.1 互斥锁std::mutex当多个线程访问共享数据时就像几个人同时编辑同一份文档必须要有协调机制。来看一个转账的例子#include mutex std::mutex g_mutex; double g_balance 1000; // 账户余额 void transferMoney(double amount) { std::lock_guardstd::mutex lock(g_mutex); g_balance amount; std::cout 转账后余额 g_balance std::endl; }这里使用了lock_guard它会在作用域结束时自动释放锁即使发生异常也能保证锁被释放。我在项目中见过有人手动调用lock()/unlock()结果因为异常导致锁永远无法释放整个程序卡死。3.2 条件变量线程间的信号灯条件变量允许线程等待特定条件成立。比如生产者-消费者模型std::mutex mtx; std::condition_variable cv; std::queueint dataQueue; void producer() { while(true) { std::unique_lockstd::mutex lock(mtx); dataQueue.push(42); cv.notify_one(); // 通知消费者 } } void consumer() { while(true) { std::unique_lockstd::mutex lock(mtx); cv.wait(lock, []{ return !dataQueue.empty(); }); int data dataQueue.front(); dataQueue.pop(); } }注意这里用了unique_lock而不是lock_guard因为wait()需要临时释放锁。4. 原子操作与无锁编程4.1 std::atomic的威力对于简单的计数器使用互斥锁太重了。原子操作是更好的选择#include atomic std::atomicint counter(0); void increment() { for(int i0; i100000; i) { counter.fetch_add(1, std::memory_order_relaxed); } }在我的性能测试中原子操作比互斥锁版本快5-10倍。但要注意内存顺序参数的选择不当使用可能导致难以发现的bug。4.2 无锁队列实战无锁数据结构是性能优化的终极武器之一。这里展示一个简单的无锁栈templatetypename T class LockFreeStack { struct Node { T data; Node* next; }; std::atomicNode* head nullptr; public: void push(const T data) { Node* newNode new Node{data, head.load()}; while(!head.compare_exchange_weak(newNode-next, newNode)); } };这种实现虽然高效但需要处理ABA问题且内存管理复杂。建议初学者先从标准库的原子类型开始。5. 性能调优实战5.1 锁粒度优化我曾经优化过一个日志系统通过减小锁粒度将吞吐量提升了40%。关键点避免在锁内执行耗时操作如I/O使用读写锁std::shared_mutex替代互斥锁考虑锁分段技术5.2 使用线程池频繁创建销毁线程代价很高。线程池是更好的选择#include vector #include functional class ThreadPool { std::vectorstd::thread workers; std::queuestd::functionvoid() tasks; // 省略同步代码... public: void enqueue(std::functionvoid() task) { // 将任务加入队列并通知工作线程 } };在8核机器上合理配置的线程池可以使任务处理速度提升6-8倍。5.3 性能分析工具Linux下我常用perf进行性能分析perf stat -e cache-misses ./my_program perf top -p pid关键指标包括缓存命中率上下文切换次数锁争用情况记得去年用perf发现一个虚假共享(false sharing)问题调整数据结构对齐后性能提升了25%。6. 常见陷阱与最佳实践死锁预防总是以固定顺序获取锁避免饥饿设置合理的线程优先级异常安全使用RAII管理资源线程局部存储thread_local关键字跨平台注意线程调度策略可能不同最后分享一个真实案例我们有个服务突然变慢最后发现是因为日志模块在锁内调用了localtime()这个函数内部有全局锁。改用localtime_r()后问题解决。多线程编程就像杂技需要平衡性能与正确性。从简单的std::thread开始逐步掌握同步原语最终你也能写出高效并发的程序。