std::mutex 与 RAII 但在后台线程中完成和发布
std::mutex with RAII but finish & release in background thread
我有一个函数,用于偶尔从GigE相机获取帧,并希望它快速返回。标准程序如下:
// ...
camera.StartCapture();
Image img=camera.GetNextFrame();
camera.StopCapture(); // <-- takes a few secs
return img;
GetNextFrame()
和StopCapture()
返回数据比较慢;因此,我想尽快返回img
并生成一个后台线程来做StopCapture()
。但是,在再次开始获取(不太可能)的情况下,我希望通过互斥锁来保护访问。有些地方可能会抛出异常,所以我决定使用raii风格的锁,它将在作用域出口释放。同时,我需要将锁转移到后台线程。类似这样的代码(伪代码):
class CamIface{
std::mutex mutex;
CameraHw camera;
public:
Image acquire(){
std::unique_lock<std::mutex> lock(mutex); // waits for cleanup after the previous call to finish
camera.StartCapture();
Image img=camera.GetNextFrame();
std::thread bg([&]{
camera.StopCapture(); // takes a long time
lock.release(); // release the lock here, somehow
});
bg.detach();
return img;
// do not destroy&release lock here, do it in the bg thread
};
};
如何将锁从调用者转移到生成的后台线程?还是有更好的办法来处理?
EDIT:确保CamIface
实例有足够的生命周期,请假设它永远存在
更新答案:@Revolver_Ocelot是对的,我的回答鼓励了未定义的行为,这是我想避免的。
那么让我使用这个So答案
中的简单信号量实现#include <mutex>
#include <thread>
#include <condition_variable>
class Semaphore {
public:
Semaphore (int count_ = 0)
: count(count_) {}
inline void notify()
{
std::unique_lock<std::mutex> lock(mtx);
count++;
cv.notify_one();
}
inline void wait()
{
std::unique_lock<std::mutex> lock(mtx);
while(count == 0){
cv.wait(lock);
}
count--;
}
private:
std::mutex mtx;
std::condition_variable cv;
int count;
};
class SemGuard
{
Semaphore* sem;
public:
SemGuard(Semaphore& semaphore) : sem(&semaphore)
{
sem->wait();
}
~SemGuard()
{
if (sem)sem->notify();
}
SemGuard(const SemGuard& other) = delete;
SemGuard& operator=(const SemGuard& other) = delete;
SemGuard(SemGuard&& other) : sem(other.sem)
{
other.sem = nullptr;
}
SemGuard& operator=(SemGuard&& other)
{
if (sem)sem->notify();
sem = other.sem;
other.sem = nullptr;
return *this;
}
};
class CamIface{
Semaphore sem;
CameraHw camera;
public:
CamIface() : sem(1){}
Image acquire(){
SemGuard guard(sem);
camera.StartCapture();
Image img=camera.GetNextFrame();
std::thread bg([&](SemGuard guard){
camera.StopCapture(); // takes a long time
}, std::move(guard));
bg.detach();
return img;
};
};
老答:就像PanicSheep说的,把互斥锁移到线程中。例如:
std::mutex mut;
void func()
{
std::unique_lock<std::mutex> lock(mut);
std::thread bg([&](std::unique_lock<std::mutex> lock)
{
camera.StopCapture(); // takes a long time
},std::move(lock));
bg.detach();
}
另外,要注意的是,不要这样做:
std::thread bg([&]()
{
std::unique_lock<std::mutex> local_lock = std::move(lock);
camera.StopCapture(); // takes a long time
local_lock.release(); // release the lock here, somehow
});
因为你正在加速线程的启动和函数作用域的结束
将std::unique_lock移至后台线程
可以同时使用mutex
和condition_variable
进行同步。此外,分离后台线程也很危险,因为当CamIface对象被销毁时,线程可能仍在运行。
class CamIface {
public:
CamIface() {
background_thread = std::thread(&CamIface::stop, this);
}
~CamIface() {
if (background_thread.joinable()) {
exit = true;
cv.notify_all();
background_thread.join();
}
}
Image acquire() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !this->stopping; });
// acquire your image here...
stopping = true;
cv.notify_all();
return img;
}
private:
void stop() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return this->stopping || this->exit; });
if (exit) return; // exit if needed.
camera.StopCapture();
stopping = false;
cv.notify_one();
}
}
std::mutex mtx;
std::condition_variable cv;
atomic<bool> stopping = {false};
atomic<bool> exit = {false};
CameraHw camera;
std::thread background_thread;
};
很难正确地做到这一点应该表明您的设计是奇怪的不对称。相反,将所有相机交互放在后台线程中,并使用来自该线程的所有互斥操作。把相机线程看作拥有相机资源和相应的互斥锁。
然后使用std::future或其他同步(如并发队列)跨线程边界交付捕获的帧。您可以从这里考虑将后台线程持久化。请注意,这并不意味着捕获必须一直运行,它可能只是使线程管理更容易:如果camera对象拥有线程,析构函数可以发出信号让它退出,然后join()
它。
- 目标C++,如何在后台线程中使用运行循环?
- 为什么我的线程不在后台运行?
- 零MQ 后台线程创建
- CredUIPromptForWindowsCredentials在后台线程中引发Microsoft C++未经处理的异
- 继承、后台线程和 RAII
- 在 Unity 中C++使用后台线程进行图像处理会导致崩溃
- 防止sleep_for阻止后台线程
- Dictach std ::线程在后台运行
- C++线程进行后台加载
- 为什么当应用在后台运行时,本机线程的行为会有所不同
- 有没有一种简单的方法可以使用SFML在后台线程中播放声音
- glewInit 从后台线程调用时失败
- 使用pthreads时的后台线程(不错,优先级)
- 使用QThread运行可管理后台线程的正确方式
- c++允许后台线程在退出应用程序之前完成
- MFC C++后台线程
- 使用提升多线程在后台运行函数
- 如何使用 C# 后台工作线程在本机C++代码中报告进度
- Kinect SDK 在后台线程上崩溃,原因不明
- 如何在后台线程上正确设置全局鼠标挂钩