线程间安全共享数据(互斥锁,队列)

94 阅读4分钟

在多个线程间安全共享图像数据并避免冲突,互斥锁(Mutex)和队列(Queue)都是有效的同步机制

1749908394934.png

1. 互斥锁方案:共享最新帧

适用场景:只需要处理最新帧(如实时预览)

#include <mutex>
#include <opencv2/opencv.hpp>

std::mutex frame_mutex;
cv::Mat current_frame;  // 共享图像

// 采集线程(生产者)
void capture_thread() {
    cv::VideoCapture cap(0);
    while (true) {
        cv::Mat frame;
        cap >> frame;
        
        {
            std::lock_guard<std::mutex> lock(frame_mutex);
            current_frame = frame.clone();  // 受保护更新
        }
    }
}

// 处理线程(消费者)
void process_thread() {
    while (true) {
        cv::Mat frame_to_process;
        {
            std::lock_guard<std::mutex> lock(frame_mutex);
            if (!current_frame.empty()) {
                frame_to_process = current_frame.clone();
            }
        }
        
        if (!frame_to_process.empty()) {
            // 安全处理图像
            detect_faces(frame_to_process);
        }
    }
}

关键要点

  1. 使用 lock_guard 实现 RAII 式锁管理
  2. 锁范围尽量小(只保护数据访问)
  3. 在锁内进行图像复制(避免返回引用)
  4. 检查空帧避免无效处理

优点

  • 内存效率高(只存一帧)
  • 总能获取最新图像
  • 实现简单直接

缺点

  • 处理线程可能重复处理同一帧
  • 锁竞争可能导致延迟
  • 死锁风险(如果嵌套加锁)

2. 队列方案:缓冲多帧

适用场景:需要处理每一帧(如视频分析)

#include <queue>
#include <mutex>
#include <condition_variable>
#include <opencv2/opencv.hpp>

const int MAX_QUEUE_SIZE = 10;

struct ThreadSafeQueue {
    std::queue<cv::Mat> frames;
    std::mutex mtx;
    std::condition_variable cond;
    
    void push(cv::Mat frame) {
        std::unique_lock<std::mutex> lock(mtx);
        // 队列满时等待空间
        cond.wait(lock, [&]{ return frames.size() < MAX_QUEUE_SIZE; });
        frames.push(std::move(frame));  // 移动语义减少拷贝
        lock.unlock();
        cond.notify_one();  // 通知消费者
    }
    
    cv::Mat pop() {
        std::unique_lock<std::mutex> lock(mtx);
        // 队列空时等待数据
        cond.wait(lock, [&]{ return !frames.empty(); });
        cv::Mat frame = std::move(frames.front());  // 移动语义
        frames.pop();
        lock.unlock();
        cond.notify_one();  // 通知生产者
        return frame;
    }
};

ThreadSafeQueue frame_queue;

// 采集线程(生产者)
void capture_thread() {
    cv::VideoCapture cap(0);
    while (true) {
        cv::Mat frame;
        cap >> frame;
        if (!frame.empty()) {
            frame_queue.push(frame.clone());
        }
    }
}

// 处理线程(消费者)
void process_thread() {
    while (true) {
        cv::Mat frame = frame_queue.pop();  // 阻塞直到有数据
        detect_faces(frame);
        // 自动释放frame内存(RAII)
    }
}

关键要点

  1. 使用条件变量(condition_variable)实现高效等待
  2. 移动语义(std::move)避免图像数据拷贝
  3. 限制队列大小防止内存溢出
  4. 双向通知(生产者与消费者协调)

优点

  • 生产消费完全解耦
  • 避免帧丢失(处理慢时缓冲)
  • 自动流量控制(队列满时阻塞生产者)
  • 无锁竞争问题

缺点

  • 内存占用更高(多帧缓冲)
  • 可能处理延迟帧(非实时最新)
  • 实现复杂度稍高

3. 混合方案:双缓冲技术

结合两种方案的优点,适用于高性能场景:

class DoubleBuffer {
    cv::Mat buffers[2];
    std::atomic<int> write_index = 0;
    std::mutex swap_mutex;
    
public:
    // 写入线程调用(无锁)
    void write(cv::Mat frame) {
        const int idx = write_index.load();
        buffers[idx] = std::move(frame);  // 移动语义
    }
    
    // 读取线程调用
    cv::Mat swap_and_read() {
        std::lock_guard<std::mutex> lock(swap_mutex);
        
        // 原子切换写入缓冲区
        const int read_index = write_index.load();
        write_index.store(1 - read_index);
        
        // 返回当前可读缓冲区
        return buffers[read_index].clone();  // 返回拷贝
    }
};

// 使用示例
DoubleBuffer frame_buffer;

void capture_thread() {
    cv::VideoCapture cap(0);
    while (true) {
        cv::Mat frame;
        cap >> frame;
        frame_buffer.write(std::move(frame));
    }
}

void process_thread() {
    while (true) {
        cv::Mat frame = frame_buffer.swap_and_read();
        if (!frame.empty()) {
            detect_faces(frame);
        }
    }
}

特点

  • 写入无锁(原子操作)
  • 交换时短暂加锁
  • 总是获取最新帧
  • 零拷贝(移动语义)
  • 无队列管理开销

4. 选择策略

  1. 优先队列方案:当满足以下条件时

    • 需要处理每一帧
    • 生产和消费速度不匹配
    • 允许少量延迟
    • 内存资源充足
  2. 选择互斥锁:当满足以下条件时

    • 只需最新帧(实时控制)
    • 内存严格受限
    • 系统简单(无复杂依赖)
  3. 选择双缓冲:当满足以下条件时

    • 高性能要求(游戏/VR)
    • 频繁写入和读取
    • 需要最低延迟
  4. 简单场景用互斥锁:单资源、低开销

  5. 稳健方案用队列:生产-消费模式、防阻塞

  6. 高性能需求用双缓冲:平衡实时性和效率

在图像处理多线程架构中,队列方案通常是最佳选择,因为它:

  • 天然匹配图像采集+处理的流水线模式
  • 自动处理速度不匹配问题
  • 减少锁竞争提升性能
  • 更容易扩展到多消费者

但最终选择应根据具体需求:

  • 实时控制系统 → 互斥锁/双缓冲
  • 视频分析系统 → 安全队列
  • 资源受限设备 → 互斥锁+原子标志