Std:: mutex с RAII, но завершение и выпуск в фоновом потоке

У меня есть функция для временного получения кадра из камеры 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 гарантировано, предположим, что оно существует навсегда.

Ответ 1

Тот факт, что это трудно сделать правильно, должно указывать на то, что ваш дизайн странно асимметричен. Вместо этого поместите все взаимодействие камеры в фоновый поток, со всеми операциями мьютекса из этого потока. Подумайте о потоке камеры как о владельце ресурса камеры и соответствующем мьютексе.

Затем доставьте захваченный кадр по границе потока с помощью std:: future или другой синхронизации, такой как параллельная очередь. Вы могли бы рассмотреть отсюда постоянство фоновой нити. Обратите внимание, что это не означает, что захват должен выполняться все время, он может просто упростить управление потоками: если объекту камеры принадлежит поток, деструктор может сигнализировать об этом, а затем join().

Ответ 2

Обновленный ответ: @Revolver_Ocelot прав, что мой ответ поощряет поведение undefined, которого я бы хотел избежать.

Итак, позвольте мне использовать простую реализацию Semaphore из этого 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
});

Потому что вы участвуете в запуске запуска потока и заканчиваете его.

Ответ 3

Переместите std:: unique_lock в фоновый поток.

Ответ 4

Для выполнения синхронизации вы можете использовать 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;
};