在条件变量中不使用谓词会导致速度变慢

Not using predicate in condition variable causing slowdown

本文关键字:速度 谓词 变量 条件      更新时间:2023-10-16

我正在尝试构建一个相机包装器,允许其他线程检索帧,而包装器在单独的线程中处理所有处理。 使用 condition 变量通知其他线程新帧已准备就绪的主要目的是使其他线程不会两次获得相同的帧。在某种程度上,这是生产者-消费者问题的一个案例。

在初始化期间,我初始化一个线程以对捕获和处理执行:

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

其中检索帧是:

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,则每个线程都只能获取 alernate 帧。但是,我希望任意数量的传入线程能够在最新帧可用时立即获取它。

在这里,这似乎是一个具有多个消费者的生产者-消费者问题,但所有消费者都应该能够获得可用的最新数据,并且不应该两次读取相同的数据。

因此,我进行了以下更改:

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;
}

现在我可以使用 2 个线程来获得相同的帧,但我注意到检索帧时有些变慢。

我运行的程序有点像这样:

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 << " FPSn";
std::cout << "t1 " << ArrayMean(time1, NFRAMES) << " t2 " << ArrayMean(time2, NFRAMES) << 'n';

相机能够以 120FPS 的帧速率抓取,所以我希望我也能够以 120FPS 的速度处理帧。

当我使用RetrieveFrames1GetFrames1运行程序时,我得到这个:

Camera took 8.39579 seconds at 119.107 FPS
t1 0.367703 t2 8.02553

但是,当我使用 RetrieveFrames2 和GetFrames2进行此测试时:

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

为什么我的线程需要更长的时间等待这里的条件变量,而我所做的唯一事情就是删除谓词?

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

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之前,您必须检查 shrared 状态。

条件变量在且仅当它们与受互斥锁保护的共享状态结合使用时才能很好地工作。调用通知函数的线程必须在互斥锁的保护下更改共享状态。等待的线程必须检查互斥锁保护下的共享状态,以确定它们是否需要等待,然后确定它们是否完成等待。

虽然可以打破这些规则并仍然让你的代码工作,但需要深厚的知识和专业知识才能可靠地使用条件变量。

为什么我的线程需要更长的时间等待这里的条件变量,而我所做的唯一事情就是删除谓词?

因为没有什么能阻止线程等待已经发生的事情。