Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a function for occasionally getting a frame from GigE camera, and want it to return quickly. The standard procedure is like this:

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

Return data is ready after GetNextFrame() and StopCapture() is quite slow; therefore, I'd like to return img as soon as possible and spawn a background thread to do StopCapture(). However, in the (unlikely) case that the acquisition is started again, I would like to protect the access by a mutex. There are places where exceptions can be thrown, so I decide to use a RAII-style lock, which will release at scope exit. At the same time, I need to transfer the lock to the background thread. Something like this (pseudocode):

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

};

How can I transfer the lock from the caller to the background thread spawned? Or is there some better way to handle this?

EDIT: Sufficient lifetime of CamIface instance is assured, please suppose it exists forever.

like image 752
eudoxos Avatar asked Sep 14 '16 06:09

eudoxos


4 Answers

Updated Answer: @Revolver_Ocelot is right that my answer encourages undefined behavior, which I'd like to avoid.

So let me use the simple Semaphore implementation from 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;
   };

};

Old Answer: Just like PanicSheep said, move the mutex into the thread. For example like this:

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

Also, just to remark, don't do this:

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

Because you're racing the thread startup and the function scope ending.

like image 195
PeterT Avatar answered Nov 17 '22 01:11

PeterT


Move the std::unique_lock to the background thread.

like image 45
Dominic Hofer Avatar answered Nov 17 '22 01:11

Dominic Hofer


You can use both mutex and condition_variable to do the synchronization. Also it's dangerous to detach the background thread, since the thread might still running while the CamIface object has been destructed.

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;
};
like image 22
for_stack Avatar answered Nov 17 '22 01:11

for_stack


The fact that this is hard to do correctly should indicate that your design is oddly asymmetric. Instead, put all of the camera interaction in the background thread, with all the mutex operations from that thread. Think of the camera thread as owning the camera resource and the corresponding mutex.

Then deliver the captured frame(s) across the thread boundary with a std::future or other synchronization like a concurrent queue. You could consider from here making the background thread persistent. Note that this doesn't mean that the capture has to run all the time, it might just make the thread management easier: if the camera object owns the thread, the destructor can signal it to exit, then join() it.

like image 1
Peter Avatar answered Nov 17 '22 03:11

Peter