std::mutex 使用 RAII 但在后台线程中完成并释放

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

我有一个偶尔从 GigE 相机获取帧的功能,并希望它快速 return。标准流程是这样的:

// ...
camera.StartCapture();
Image img=camera.GetNextFrame();
camera.StopCapture(); // <--  takes a few secs
return img;

Return数据在GetNextFrame()之后准备就绪,StopCapture()相当慢;因此,我想尽快 return 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
   };

};

如何将锁从调用者转移到生成的后台线程?或者有更好的方法来处理这个问题吗?

编辑: CamIface 实例的足够生命周期是有保证的,请假设它永远存在。

将 std::unique_lock 移动到后台线程。

更新答案: @Revolver_Ocelot 是正确的,我的回答鼓励未定义的行为,我想避免这种行为。

所以让我使用来自this SO Answer

的简单信号量实现
#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说的,把mutex移到线程里。例如像这样:

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

因为您正在加速线程启动和函数作用域结束。

您可以同时使用 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 或其他同步(如并发队列)跨线程边界传送捕获的帧。您可以从这里考虑使后台线程持久化。请注意,这并不意味着捕获必须一直 运行,它可能只是使线程管理更容易:如果相机对象拥有线程,析构函数可以发出信号让其退出,然后 join()它。