c++ 未在条件变量中使用 predicate 导致速度减慢

f87krz0w  于 2023-06-25  发布在  其他
关注(0)|答案(2)|浏览(106)

我正在尝试构建一个相机 Package 器,允许其他线程检索帧,而 Package 器在一个单独的线程中处理所有处理。使用条件变量通知其他线程新帧准备就绪的主要目的是使其他线程不会两次获得同一帧。在某种程度上,这是一个生产者-消费者问题的例子。
在初始化过程中,我初始化了一个线程来进行捕获和处理:

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

其中RetrieveFrames是:

int PiCamera::RetrieveFrames1(){
while(camera_on_){
    camera_.grab();
    frame_ready_ = true;
    ready_condition_.notify_all(); // Notify on condition variable
}
return 0;
}

现在,当有一个线程试图获取一个帧时,线程需要调用的是:

int PiCamera::GetFrame1(cv::Mat &image){
    // Lock mutex
    std::unique_lock<std::mutex> mutex_lock(ready_mutex_);
    ready_condition_.wait(mutex_lock, [this](){return frame_ready_;});
    camera_.retrieve(image);
    frame_ready_ = false;
    // Unlock mutex
    mutex_lock.unlock();
    return 0;
}

现在,如果两个线程调用函数GetFrame,它们中的每一个都只能获得警报帧。但是,我希望任何数量的传入线程都能够在最新帧可用时立即获得它。
在这里,这似乎是一个生产者-消费者的问题,多个消费者,但所有消费者都应该能够获得最新的可用数据,并且不应该读取相同的数据两次。
因此,我做了以下修改:

int PiCamera::RetrieveFrames2(){
    while(camera_on_){
        camera_.grab();
        // frame_ready_ = true;
        ready_condition_.notify_all(); // Notify on condition variable
    }
    return 0;
}

int PiCamera::GetFrame2(cv::Mat &image){
    // Lock mutex
    std::unique_lock<std::mutex> mutex_lock(ready_mutex_);
    // ready_condition_.wait(mutex_lock, [this](){return frame_ready_;});
    ready_condition_.wait(mutex_lock);
    camera_.retrieve(image);
    // frame_ready_ = false;
    // Unlock mutex
    mutex_lock.unlock();
    return 0;
}

现在我可以使用两个线程来获取相同的帧,但是我注意到检索帧的速度有些慢。
我运行的程序是这样的:

PiCamera camera();
camera.Init();
cv::Point2f centroid_location;
cv::Mat image;
float time1[NFRAMES] = {};
float time2[NFRAMES] = {};
float time3[NFRAMES] = {};
timeval tstart, tend, t1, t2, t3;

for(int frame=0;frame<NFRAMES;frame++){
    gettimeofday(&t1, nullptr);
    camera->GetFrame(image);
    gettimeofday(&t2, nullptr);
    time1[frame] = ElapsedSec(t1, t2)*1000;
    GetCentroid(image, centroid_location);
    // Just to increase workload
    GetCentroid(image, centroid_location);
    gettimeofday(&t3, nullptr);
    time2[frame] = ElapsedSec(t2, t3)*1000;
}

gettimeofday(&tend, nullptr);
float total_time = ElapsedSec(tstart, tend);
float fps = (float)NFRAMES/total_time;
std::cout << "Camera took " << total_time << " seconds at " << fps << " FPS\n";
std::cout << "t1 " << ArrayMean(time1, NFRAMES) << " t2 " << ArrayMean(time2, NFRAMES) << '\n';

相机能够以120 fps的帧率抓取,所以我希望我也能够以120 fps的帧率处理帧。
当我使用 * RetrieveFrames 1 * 和 * GetFrames 1 * 运行程序时,我得到了这个:

Camera took 8.39579 seconds at 119.107 FPS
t1 0.367703 t2 8.02553

但是,当我使用 * RetrieveFrames 2 * 和 * GetFrames 2 * 进行此测试时:

Camera took 13.7088 seconds at 72.946 FPS
t1 4.74955 t2 8.95662

即使我调用 GetCentroid 一次,我也会得到以下结果:

Camera took 8.29365 seconds at 120.574 FPS
t1 3.98624 t2 4.30591

Camera took 8.94322 seconds at 111.817 FPS
t1 4.43984 t2 4.50159

为什么我的线程要花这么长的时间来等待条件变量,而我所做的只是删除 predicate ?

6jygbczu

6jygbczu1#

为了防止任何人遇到这种情况,我最终使用https://github.com/rigtorp/MPMCQueue来抽象出在消费者线程和生产者线程之间传输数据的逻辑

0pizxfdo

0pizxfdo2#

int PiCamera::RetrieveFrames1(){
while(camera_on_){
    camera_.grab();
    frame_ready_ = true;
    ready_condition_.notify_all(); // Notify on condition variable
}

这是因为这个线程修改了frame_ready_而没有保存保护它的互斥锁。

int PiCamera::RetrieveFrames2(){
    while(camera_on_){
        camera_.grab();
        // frame_ready_ = true;
        ready_condition_.notify_all(); // Notify on condition variable
    }
    return 0;

这是因为它调用notify_all而不改变任何受互斥锁保护的共享状态。您必须在调用notify_all之前或调用notify_all之后但在释放互斥体之前更改共享状态。

int PiCamera::GetFrame2(cv::Mat &image){
    // Lock mutex
    std::unique_lock<std::mutex> mutex_lock(ready_mutex_);
    // ready_condition_.wait(mutex_lock, [this](){return frame_ready_;});
    ready_condition_.wait(mutex_lock);
    camera_.retrieve(image);
    // frame_ready_ = false;
    // Unlock mutex
    mutex_lock.unlock();
    return 0;
}

这是因为它调用wait时没有首先检查互斥体保护的共享状态是否处于它正在等待的状态之外。你不能等待已经发生的事情。在调用wait之前,必须检查碎片状态。
条件变量当且仅当它们与受互斥锁保护的共享状态结合使用时才能正常工作。调用notify函数的线程必须在互斥锁的保护下更改共享状态。等待的线程必须检查互斥锁保护下的共享状态,以确定它们是否需要等待,然后确定它们是否已经完成等待。
虽然有可能打破这些规则,但仍然可以让代码正常工作,但要可靠地使用条件变量需要深厚的知识和专业知识。
为什么我的线程要花这么长的时间来等待条件变量,而我所做的只是删除 predicate ?
因为没有什么可以阻止线程等待已经发生的事情。

相关问题