并发 (Concurrency)是指同时管理多个任务的能力,这些任务可以交替执行。
第一阶段:标准库基础 (C++11)
这是起点,你需要熟悉<thread>库。
核心概念:什么是线程?主线程与子线程的关系。
API:
std::thread的创建、join()(等待线程结束)、detach()(分离线程)。生命周期:理解如果
std::thread对象销毁前没有 join 或 detach 会导致程序崩溃 (terminate)。补充:thread_local关键字。
第二阶段:同步与互斥 (数据安全)
多线程最难的地方不是开启线程,而是如何安全地访问共享数据。如果不加控制,就会发生竞态条件 (Race Condition)。
互斥锁:
std::mutex。RAII 锁管理(必须掌握):永远不要手动
lock()和unlock(),要使用std::lock_guard和std::unique_lock。条件变量:
std::condition_variable,用于线程间的通信(例如:生产者-消费者模型)原子操作:
std::atomic(解决共享数据的原子性访问和内存可见性。对于简单的整数加减,比锁快得多)。注意:
std::lock_guard和std::unique_lock的区别。补充:C++17 scoped_lock,支持同时获取多个锁
第三阶段:异步与高级抽象 (C++11/14/17)
有时候你不需要管理原始线程,只需要结果。
Future/Promise:
std::future,std::promise,std::packaged_task。( promise写值,写异常。 future读值,或抛出异常。packaged_task把可调用对象包装成一个异步任务,内部有一个 promise。)自动异步:
std::async(最简单的多线程启动方式,返回值为std::future类型)。基于线程(std::thread)的并发和基于任务(std::async)的并发:
第四阶段:实战与架构 (设计模式)
线程池 (Thread Pool):避免频繁创建销毁线程的开销。
死锁 (Deadlock):如何避免多个锁互相等待。
C++20 新特性:
std::jthread(自动 join),std::latch,std::barrier。
注意,在实际生产中,很少直接用std::thread启动任务,而是用线程池。这里有一个视频讲得不错:
基于C++11实现的异步线程池【C/C++】_哔哩哔哩_bilibili
## 线程池是否有必要?
### ✅ 有必要的情况
1. 存在并发图片流 如果有多张图片持续涌入(比如相机连拍、多路摄像头同时上传),线程池能让它们 并行处理 ,充分利用多核 CPU。
2. 处理耗时较长`cv::resize` + JPEG 编码是 CPU 密集型操作。如果单张处理需要几十毫秒,串行处理会造成积压,线程池能提升整体吞吐量。
3. 需要削峰填谷 图片到达是突发的,但处理是匀速的。线程池 + 任务队列可以 缓冲峰值 ,避免瞬时压力打满 CPU。
### ❌ 没必要的情况
1. 图片是串行到达的 如果一次只处理一张,处理完才来下一张,线程池和单线程效果一样,反而多了线程切换开销。
2. OpenCV 内部已多线程,造成过度订阅 这是最容易被忽略的点。OpenCV 的`cv::resize` 、`cv::imwrite` 等函数 内部已经使用多线程 (TBB/OpenMP)。如果外层线程池开 8 个线程,每个线程里 OpenCV 又开 8 个线程,就是 8×8=64 个线程争抢 CPU ,反而比单线程更慢。
3. 瓶颈在磁盘 I/O 而非 CPU 如果图片很大,主要耗时在磁盘读写,多线程并行写盘会导致 磁头频繁寻道 (HDD)或 SSD 写入带宽争用,性能不升反降。