> 技术文档 > 【记录】C++生产者 / 消费者 案例

【记录】C++生产者 / 消费者 案例


一、实现流程

1. 定义线程安全的队列结构

需要封装std::queue,用std::mutex保证互斥访问,用std::condition_variable触发唤醒:

#include #include #include #include  // 假设用OpenCV存储图像// 线程安全的图像队列class ImageQueue {private: std::queue queue_; // 存储图像数据 std::mutex mtx_;  // 互斥锁,保护队列操作 std::condition_variable cv_; // 条件变量,用于线程同步 bool is_stopped_ = false; // 退出标志(用于优雅终止线程)public: // 生产者调用:放入图像 void push(const cv::Mat& img) { std::lock_guard lock(mtx_); // 自动加锁/解锁 queue_.push(img); cv_.notify_one(); // 通知等待的消费者(唤醒一个线程) } // 消费者调用:取出图像(无数据时阻塞) bool pop(cv::Mat& img) { std::unique_lock lock(mtx_); // 可手动解锁的锁 // 等待条件:队列非空 或 线程需终止 cv_.wait(lock, [this]{ return !queue_.empty() || is_stopped_; }); if (is_stopped_) return false; // 线程终止,退出 img = queue_.front(); queue_.pop(); return true; } // 终止线程时调用 void stop() { std::lock_guard lock(mtx_); is_stopped_ = true; cv_.notify_all(); // 唤醒所有等待的线程 }};
2. 实现生产者线程(相机采集)

生产者的核心是快速获取图像并放入队列,避免在回调 / 采集函数中做耗时操作:

#include #include  // 替换为实际相机SDKImageQueue img_queue; // 全局队列(或通过指针传递)bool is_running = true;// 相机回调函数(生产者的核心逻辑)void cameraCallback(const CameraImage& raw_img) { if (!is_running) return; // 轻量操作:将原始图像转为OpenCV格式(快速转换,避免复杂计算) cv::Mat img(raw_img.height, raw_img.width, CV_8UC3, raw_img.data); // 放入队列(耗时极短) img_queue.push(img);}// 启动相机采集线程void startProducer() { // 初始化相机(根据SDK文档配置) Camera::init(); Camera::setCallback(cameraCallback); // 设置回调 Camera::startCapture(); // 开始采集(如30fps) // 保持线程运行(或由SDK内部线程驱动) while (is_running) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } Camera::stopCapture(); Camera::release();}
3. 实现消费者线程(图像处理)

消费者从队列中取图并处理,处理速度慢也不会阻塞生产者(队列会暂存数据):

// 图像处理函数(耗时操作)void processImage(const cv::Mat& img) { cv::Mat gray; cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY); // 示例:转灰度 cv::GaussianBlur(gray, gray, cv::Size(5,5), 0); // 高斯模糊 // ... 其他耗时操作(如目标检测、特征提取等)}// 消费者线程void startConsumer() { cv::Mat img; while (img_queue.pop(img)) { // 无数据时阻塞,不占用CPU processImage(img); // 处理图像(耗时操作) }}
4. 主线程控制启动与退出
int main() { // 启动生产者和消费者线程 std::thread producer_thread(startProducer); std::thread consumer_thread(startConsumer); // 运行一段时间(或等待用户输入退出) std::cout << \"按Enter键退出...\" << std::endl; std::cin.get(); // 终止流程 is_running = false; // 停止生产者 img_queue.stop(); // 通知消费者退出 producer_thread.join(); // 等待生产者线程结束 consumer_thread.join(); // 等待消费者线程结束 return 0;}

二、关键优势

  1. 避免 CPU 空转:消费者无数据时通过cv.wait()进入休眠,不占用 CPU;生产者放入数据后通过cv.notify_one()唤醒,仅在必要时工作。
  2. 解耦采集与处理:相机采集(快)和图像处理(慢)独立运行,采集线程不会被处理耗时阻塞,避免丢帧;处理线程也不会因采集速度快而超负荷。
  3. 灵活扩展:可根据需求增加多个消费者线程(如 2 个处理线程并行处理队列数据),充分利用多核 CPU:
    // 启动多个消费者线程std::vector consumers;for (int i = 0; i < 2; ++i) { consumers.emplace_back(startConsumer);}

三、注意事项

  1. 队列长度控制:若处理速度长期慢于采集速度,队列会无限增长导致内存溢出。可在push时增加限制:
    void push(const cv::Mat& img) { std::lock_guard lock(mtx_); if (queue_.size() > 10) { // 限制最大缓存10帧 queue_.pop(); // 丢弃最旧的帧 } queue_.push(img); cv_.notify_one();}
  2. 图像数据拷贝:若图像数据较大,push时的cv::Mat拷贝(浅拷贝,仅复制头信息)是高效的,但需确保原始数据生命周期足够长(避免相机 SDK 过早释放)。
  3. 线程安全:所有对队列的操作必须加锁,否则可能导致队列数据混乱或崩溃。