You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

未在condition variable中使用谓词导致性能下降的原因问询

为什么移除条件变量的谓词后,帧获取时间大幅增加?

你的问题核心在于对条件变量的使用逻辑理解偏差,以及多线程同步时的资源竞争问题。我们来一步步拆解原因,再给出符合需求的解决方案。

先看两个版本的核心差异

版本1(带frame_ready_和谓词)

wait的谓词[this](){return frame_ready_;}本质是一个循环校验:

while (!frame_ready_) {
    ready_condition_.wait(mutex_lock);
}

当生产者调用notify_all()后,所有等待线程被唤醒,但只有第一个拿到mutex的线程会看到frame_ready_ == true,执行retrieve后将frame_ready_设为false。其他线程唤醒后校验frame_ready_为false,会自动回到等待状态(释放mutex),不会执行retrieve。

这意味着每次新帧就绪后,只有一个线程会执行retrieve操作,没有多余的线程竞争和串行执行开销,所以GetFrame的耗时很短。

版本2(移除谓词和frame_ready_)

ready_condition_.wait(mutex_lock)没有校验逻辑,线程被唤醒后(不管是notify_all还是虚假唤醒)会直接执行后续代码,这会带来三个关键问题:

  1. 线程串行竞争:notify_all会唤醒所有等待线程,这些线程会排队竞争ready_mutex_,每个线程拿到锁后都要执行camera_.retrieve(image)。而retrieve本身涉及硬件访问或缓冲区拷贝,多个线程串行执行会大幅增加单帧获取时间。
  2. 虚假唤醒浪费资源:线程可能被无意义地唤醒(虚假唤醒),此时没有新帧却依然执行retrieve,进一步增加等待耗时。
  3. 相机操作线程不安全:camera_.grab()和camera_.retrieve()通常不是线程安全的,版本2中两者可能并发执行,导致retrieve需要等待grab完成,额外增加了延迟。

这就是为什么版本2的t1(GetFrame耗时)从0.36ms暴涨到4.7ms的原因。

实现你需求的正确方案

你想要的是:任意数量的线程都能在新帧就绪时获取最新帧,且每个新帧可被所有线程读取一次。我们可以用版本号同步+帧缓存的方式实现:

// 类成员变量
std::atomic<int> frame_version_ = 0;
std::mutex ready_mutex_;
std::condition_variable ready_condition_;
bool camera_on_ = false;
std::thread capture_thread_;
cv::Mat latest_frame_; // 缓存最新帧,避免消费者直接操作相机

int PiCamera::Init(){
 camera_on_ = true;
 capture_thread_ = std::thread(&PiCamera::RetrieveFrames, this);
 return 0;
}

int PiCamera::RetrieveFrames(){
 while(camera_on_){
    cv::Mat temp_frame;
    camera_.grab();
    camera_.retrieve(temp_frame); // 生产者线程完成retrieve,避免消费者线程操作相机
    
    // 锁定更新最新帧和版本号
    std::lock_guard<std::mutex> lock(ready_mutex_);
    latest_frame_ = temp_frame.clone(); // 克隆帧,防止消费者访问时被覆盖
    frame_version_++;
    ready_condition_.notify_all();
 }
 return 0;
}

int PiCamera::GetFrame(cv::Mat &image){
 int last_version = frame_version_;
 std::unique_lock<std::mutex> mutex_lock(ready_mutex_);
 // 等待版本号更新(确保拿到新帧)
 ready_condition_.wait(mutex_lock, [this, last_version](){return frame_version_ > last_version;});
 // 拷贝最新帧
 image = latest_frame_.clone();
 mutex_lock.unlock();
 return 0;
}

这个方案的优势:

  • 线程安全:生产者线程单独负责相机的grab和retrieve,消费者只操作缓存的帧,避免了相机操作的并发冲突。
  • 高效同步:用frame_version_作为新帧的标志,每个消费者线程只会等待比自己上次获取的版本更高的帧,确保拿到最新数据。
  • 无冗余开销:notify_all唤醒所有线程后,每个线程都能直接拷贝缓存的帧,不需要串行执行retrieve,帧率可以接近相机的抓取上限。

内容的提问来源于stack exchange,提问作者akshatd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:10:19