Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a smart pointer that locks and unlocks a mutex?

I have a threaded class from which I would like to occasionally acquire a pointer an instance variable. I would like this access to be guarded by a mutex so that the thread is blocked from accessing this resource until the client is finished with its pointer.

My initial approach to this is to return a pair of objects: one a pointer to the resource and one a shared_ptr to a lock object on the mutex. This shared_ptr holds the only reference to the lock object so the mutex should be unlocked when it goes out of scope. Something like this:

void A::getResource()
{
    Lock* lock = new Lock(&mMutex);
    return pair<Resource*, shared_ptr<Lock> >(
        &mResource, 
        shared_ptr<Lock>(lock));
}

This solution is less than ideal because it requires the client to hold onto the entire pair of objects. Behaviour like this breaks the thread safety:

Resource* r = a.getResource().first;

In addition, my own implementation of this is deadlocking and I'm having difficulty determining why, so there may be other things wrong with it.

What I would like to have is a shared_ptr that contains the lock as an instance variable, binding it with the means to access the resource. This seems like something that should have an established design pattern but having done some research I'm surprised to find it quite hard to come across.

My questions are:

  • Is there a common implementation of this pattern?
  • Are there issues with putting a mutex inside a shared_ptr that I'm overlooking that prevent this pattern from being widespread?
  • Is there a good reason not to implement my own shared_ptr class to implement this pattern?

(NB I'm working on a codebase that uses Qt but unfortunately cannot use boost in this case. However, answers involving boost are still of general interest.)

like image 947
Tim MB Avatar asked Apr 08 '13 10:04

Tim MB


People also ask

Which type of smart pointer creates a control block?

A control block is created when a std::shared_ptr is constructed from a unique-ownership pointer (i.e., a std::unique_ptr or std::auto_ptr ). Unique-ownership pointers don't use control blocks, so there should be no control block for the pointed-to object.

How do I lock my mutex?

First, we need to declare a lock variable of type Mutex. Second, before entering the critical section that is protected by that lock, execute the Lock() method of that lock. Third, before exiting the critical section, execute the Unlock() method of the lock.

Does shared_ptr use mutex?

Well, in mt_shared_ptr, there is a mutex that gets locks for every operation performed on it. So when you call f. reset(), call . shared() or re-assign it, it locks the mutex first, so the access to the internal shared_ptr<> is always exclusive.


1 Answers

I'm not sure if there are any standard implementations, but since I like re-implementing stuff for no reason, here's a version that should work (assuming you don't want to be able to copy such pointers):

template<class T>
class locking_ptr
{
public:
  locking_ptr(T* ptr, mutex* lock)
    : m_ptr(ptr)
    , m_mutex(lock)
  {
    m_mutex->lock();
  }
  ~locking_ptr()
  {
    if (m_mutex)
      m_mutex->unlock();
  }
  locking_ptr(locking_ptr<T>&& ptr)
    : m_ptr(ptr.m_ptr)
    , m_mutex(ptr.m_mutex)
  {
    ptr.m_ptr = nullptr;
    ptr.m_mutex = nullptr;
  }

  T* operator ->()
  {
    return m_ptr;
  }
  T const* operator ->() const
  {
    return m_ptr;
  }
private:
  // disallow copy/assignment
  locking_ptr(locking_ptr<T> const& ptr)
  {
  }
  locking_ptr& operator = (locking_ptr<T> const& ptr)
  {
    return *this;
  }
  T* m_ptr;
  mutex* m_mutex; // whatever implementation you use
};
like image 102
riv Avatar answered Oct 14 '22 22:10

riv