std::mutex 与 RAII 但在后台线程中完成和发布

std::mutex with RAII but finish & release in background thread

本文关键字:线程 后台 mutex RAII std      更新时间:2023-10-16

我有一个函数,用于偶尔从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移至后台线程

可以同时使用mutexcondition_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()它。