1. 为什么需要多线程编程想象你正在餐厅吃饭服务员需要同时处理多个任务点单、上菜、结账。如果只有一个服务员按顺序处理这些请求顾客等待时间会很长。多线程就像雇佣多个服务员让餐厅服务效率成倍提升。在计算机世界中多线程能让程序同时处理多个任务。比如浏览器可以一边下载文件一边渲染页面游戏可以在处理物理引擎的同时响应用户输入。C11之前开发者需要使用平台特定的API如Windows的CreateThread或Linux的pthread现在有了标准化的std::thread代码可以跨平台运行。我曾在图像处理项目中遇到性能瓶颈单线程处理4000x3000像素图片需要3秒改用4个线程后时间缩短到0.8秒。这就是多线程的魅力——合理利用CPU多核能力。2. std::thread基础用法2.1 创建第一个线程#include iostream #include thread void hello() { std::cout Hello from thread!\n; } int main() { std::thread t(hello); // 线程立即启动 t.join(); // 等待线程结束 return 0; }这个简单例子展示了线程创建的核心步骤包含 头文件定义线程函数这里用普通函数hello构造std::thread对象时传入函数调用join等待线程结束新手常犯的错误是忘记join或detach这会导致程序终止。记住每个线程对象在销毁前必须调用join()或detach()。2.2 三种创建线程的方式除了普通函数std::thread还支持多种可调用对象Lambda表达式推荐std::thread t([](){ std::cout Lambda thread\n; });函数对象struct Task { void operator()(int n) const { std::cout Processing n \n; } }; std::thread t(Task(), 42);类成员函数class Worker { public: void run(int id) { std::cout Worker id running\n; } }; Worker w; std::thread t(Worker::run, w, 1);实际项目中我更喜欢用lambda表达式因为它可以直接捕获局部变量代码更紧凑。函数对象适合复杂任务而成员函数方式则适合面向对象设计。3. 线程参数传递技巧3.1 值传递与引用传递默认情况下参数会被拷贝到线程内部void modify(int n) { n 10; } int main() { int x 5; std::thread t(modify, x); t.join(); std::cout x; // 输出5x未被修改 }要传递引用必须使用std::refvoid modify(int n) { n 10; } int main() { int x 5; std::thread t(modify, std::ref(x)); t.join(); std::cout x; // 输出10 }3.2 移动语义的应用对于不可复制的对象如unique_ptr可以使用移动语义std::unique_ptrint ptr(new int(42)); std::thread t([](std::unique_ptrint p){ std::cout *p \n; }, std::move(ptr));我曾在一个网络项目中用这种方式将套接字所有权转移给工作线程避免了复杂的锁机制。4. 线程生命周期管理4.1 join与detach的抉择join()阻塞当前线程直到目标线程完成detach()让线程在后台自主运行std::thread t(heavy_task); // 方案1等待任务完成 t.join(); // 方案2分离线程风险 t.detach();经验法则除非有特殊需求否则优先使用join。detach的线程就像脱缰野马难以追踪和管理。我曾调试过一个detach线程访问已销毁对象的bug花了整整两天。4.2 使用RAII自动管理为避免异常导致线程未join可以实现一个守卫类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 foo() { std::thread t([]{ /*...*/ }); ThreadGuard g(t); // 即使这里抛出异常线程也会被正确join }5. 线程同步机制5.1 mutex基础用法std::mutex mtx; int shared_data 0; void safe_increment() { std::lock_guardstd::mutex lock(mtx); shared_data; }lock_guard在构造时加锁析构时自动解锁即使发生异常也能保证锁被释放。这是我最常用的RAII锁类型。5.2 避免死锁的几种方法按固定顺序上锁std::mutex mtx1, mtx2; // 线程A std::lock(mtx1, mtx2); // 同时锁定多个互斥量 std::lock_guardstd::mutex lk1(mtx1, std::adopt_lock); std::lock_guardstd::mutex lk2(mtx2, std::adopt_lock); // 线程B同样顺序 std::lock(mtx1, mtx2); // ...使用std::unique_lockstd::mutex mtx; std::unique_lockstd::mutex lock(mtx, std::defer_lock); lock.lock(); // 可以手动控制加锁时机 // ... lock.unlock(); // 也可以提前解锁设置超时std::timed_mutex mtx; if(mtx.try_lock_for(std::chrono::milliseconds(100))) { // 获取锁成功 mtx.unlock(); }在实际项目中我曾遇到过一个经典死锁场景线程A持有锁1请求锁2线程B持有锁2请求锁1。通过统一锁的获取顺序解决了这个问题。6. 高级线程通信6.1 condition_variable使用模式生产者-消费者模型的典型实现std::mutex mtx; std::queueint data_queue; std::condition_variable data_cond; void producer() { while(true) { std::unique_lockstd::mutex lock(mtx); data_queue.push(42); data_cond.notify_one(); lock.unlock(); std::this_thread::sleep_for(std::chrono::seconds(1)); } } void consumer() { while(true) { std::unique_lockstd::mutex lock(mtx); data_cond.wait(lock, []{ return !data_queue.empty(); }); int data data_queue.front(); data_queue.pop(); lock.unlock(); process(data); } }condition_variable的wait操作会自动释放锁并阻塞直到被唤醒后重新获取锁。这种机制比轮询高效得多。6.2 future/promise模式std::promiseint prom; std::futureint fut prom.get_future(); std::thread t([](std::promiseint p){ p.set_value(42); // 设置值 }, std::move(prom)); std::cout fut.get(); // 获取结果这个模式非常适合需要获取异步任务结果的场景。我在一个分布式计算项目中用它来收集各个工作节点的计算结果。7. 性能优化技巧7.1 原子操作替代锁std::atomicint counter(0); void safe_increment() { counter; // 原子操作无需锁 }对于简单数据类型原子操作比互斥锁快10-100倍。但要注意原子操作不是万能的复杂操作仍需锁。7.2 线程局部存储thread_local int thread_spec 0; void use_tls() { thread_spec; // 每个线程有自己的副本 }适合存储线程特定的状态比如随机数生成器、数据库连接等。我曾用它实现线程安全的日志系统每个线程有自己的日志缓冲区。7.3 线程池模式避免频繁创建销毁线程的开销class ThreadPool { std::vectorstd::thread workers; std::queuestd::functionvoid() tasks; // ... 同步原语 public: void enqueue(std::functionvoid() task) { // 将任务加入队列并通知工作线程 } // ... };线程池是实际项目中的标配。根据我的测试对于短任务线程池比每次新建线程快20倍以上。8. 常见陷阱与解决方案悬垂引用线程使用已销毁的局部变量void danger() { int x 42; std::thread t([x]{ std::cout x; }); // 错误 t.detach(); } // x被销毁线程可能还在运行异常安全线程函数抛出异常未捕获void safe_thread() noexcept try { // 线程代码 } catch(...) { // 记录日志 }资源泄漏忘记join或detachstd::thread t([]{}); if(t.joinable()) t.join(); // 必须检查虚假共享多个线程频繁修改同一缓存行的不同变量struct alignas(64) CacheLine { // 64字节对齐 int data1; int data2; };在多线程开发中这些陷阱我都踩过。最惨痛的一次是虚假共享导致性能下降80%最后通过内存对齐解决。