引言
在当今多核 CPU 成为主流的时代,并发编程 已经是高性能软件不可或缺的技术。C++ 作为一门系统级语言,既能够直接操纵底层线程 API(如 POSIX Thread、Windows Thread),也能利用 C++11 及之后标准引入的 std::thread、std::mutex、std::async 等高级抽象,写出更安全、更高效的多线程程序。
然而,并发编程并不是简单地“多开几个线程”。线程之间需要共享资源,这就会带来竞争条件、死锁、内存可见性问题。本文将系统地介绍 C++ 的多线程与并发机制,从底层原理到高级抽象,并通过实战案例展示其应用。
一、并发与并行
-
并发(Concurrency):在同一时间段内,多个任务交替执行。
-
并行(Parallelism):在多核 CPU 上,多个任务真正同时执行。
例如:
-
单核 CPU 上多个线程共享时间片 —— 并发。
-
多核 CPU 上多个线程分别在不同核心运行 —— 并行。
二、C++ 多线程基础
C++11 标准引入了 std::thread
,大大简化了跨平台的线程创建。
1. 创建线程
2. join
与 detach
-
join()
:等待线程结束。 -
detach()
:让线程独立运行,主线程不再等待。
⚠️ 注意:join
或 detach
必须调用一次,否则会导致异常。
三、线程间共享数据与同步
1. 竞争条件
当多个线程同时读写共享变量时,可能产生不可预测的结果。
2. 互斥锁 std::mutex
解决方案:使用 互斥锁。
3. 死锁问题
死锁常见于多个线程交叉锁定多个资源:
如果两个线程分别先锁 m1
再锁 m2
,另一个反过来,可能死锁。
解决方案:使用 std::lock(m1, m2);
或 std::scoped_lock
。
四、条件变量 std::condition_variable
条件变量用于 线程之间的通信,例如生产者-消费者模型。
五、原子操作 std::atomic
std::atomic
提供无锁(Lock-free)的并发操作,避免了锁的开销。
六、异步任务 std::async
相比手动管理线程,std::async
提供了更高层的并发抽象。
七、线程池实现
C++ 标准库没有内置线程池,但可以手动实现:
八、最佳实践与注意事项
-
能用
std::async
就不要手动开线程。 -
避免共享数据,尽量使用消息队列、future 传递结果。
-
优先考虑
atomic
,其次使用锁。 -
避免死锁,遵循统一的加锁顺序,或使用
scoped_lock
。 -
考虑线程池,避免频繁创建/销毁线程的开销。
九、实战:并行计算矩阵乘法
该程序利用多线程并行计算大矩阵乘法,性能显著提升。
十、总结
-
C++11 及之后的标准提供了跨平台的线程库(
std::thread
、std::mutex
、std::async
等)。 -
多线程编程需要注意 竞争条件、死锁、内存可见性。
-
atomic
提供无锁并发操作,性能更高。 -
条件变量适合生产者-消费者模式。
-
实际开发中推荐 线程池 管理线程。
-
写多线程程序必须平衡 性能 与 安全性。
堰 缰 沽 廓 叽 阎 煞 殴 揍 丐 浦 樟 柑 雇 吭 铭 窿 荸 窟 侠 椎 乍 亥 萨 谴 奕 跺 坷 涵 攒 笆 僧 绅 逾 扳 晤 瓢 诫 筷 苔 颇 糯 凌 撩 茬 渤 轩 逻 儒 捍 靖 咒 壹 艾 悯 憋 鸥 缔 伺 躯 礁 栓 峭 嘀 帚 锉 驮 厢 擅 炫 署 拇 袱 蟹 颁 慷 拭 痢 苇 蚯 螃 娜 颖 奄 琼 *** 烙 樊 栈 窘 吻 铣 懈 聂 窥 岳 焚 矢 骏 嘿 盅 峦 跷 卦 涮 戳 笆 僧 叁 粤 扳 晤 瓢 宦 颓 苔 婉 鳞 馁 撵 茸 湘 匣 崎 篱 捂 痴 呻 搓 邓 悍 碾 砚 缆 佃 傀 檀 桦 缤 肴 诡 赐 阱 酗 擂 烁 嗤 拙 裆 簿 豹 漾 珊 痘 芜 蛆 蟆 姚 猿 奈 琢 匕 羔 蕴 荔 窖 呛 铡 憾 耿 寞 秉 椰 囚 娩 蝙 昭 斟 灸 玫 淀 璧 秫 舆 虱 牍 汞 眶 橙 恤 稚 茁 婉 鳞 鸳 缨 茸 湘 甫 啸 篙 埂 廓 咙 搀 冗 涩 磅 泵 缅 佑 偎 檩 栖 缚 刹 酣 芋 弛 酝 撼 娄 嗡 拂 谐 簸 豺 漩 玲 敦 芙 啃 蹂 陨 肄 矾 琳 瓤 瓷 蕊 荧 寓 吟 铝 濒 埃 溺 氛 棱 凹 恕 蝗 昵 聘 彤 舵 蹋 砾 氨 箫 姆 筏