std :: mutex with RAII但是完成&在后台线程中发布

时间:2016-09-14 06:53:33

标签: c++ multithreading c++11 mutex

我有一个偶尔从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
   };

};

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

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

4 个答案:

答案 0 :(得分:7)

更新答案: @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所说的那样,将互斥锁移动到线程中。例如:

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

因为您正在参与线程启动和功能范围的结束。

答案 1 :(得分:3)

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

答案 2 :(得分:3)

您可以同时使用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;
};

答案 3 :(得分:1)

这很难做到的事实应该表明你的设计是奇怪的不对称。相反,将所有相机交互放在后台线程中,并使用该线程中的所有互斥操作。将相机线程视为拥有相机资源和相应的互斥锁。

然后使用std :: future或其他同步(如并发队列)跨线程边界传递捕获的帧。您可以从这里考虑使后台线程持久化。请注意,这并不意味着捕获必须始终运行,它可能只是使线程管理更容易:如果摄像机对象拥有线程,析构函数可以发出信号退出,然后join()它。