Is this an acceptable way to lock a container using C ++?

I need to implement (in C ++) a container protected by threads so that only one thread can add or remove elements from the container. I used to do this before, sharing a mutex between threads. This leads to the fact that many mutex objects are dotted all over my code and make things very dirty and difficult to maintain.

I was wondering if there is a tidier and more object oriented way to do this. I thought of the following simple class wrapper around a container (semi-simple C ++ code)

 class LockedList {
    private:
        std::list<MyClass> m_List;

    public:
        MutexObject Mutex;
 };

so the lock can be done as follows

 LockedList lockableList;     //create instance
 lockableList.Mutex.Lock();    // Lock object

 ... // search and add or remove items

 lockableList.Mutex.Unlock();   // Unlock object

, , , ? , , - . - ?

++ , .

, - - , , ??? .

+5
6

resourece, , . , automatically .

template<typename Resource>
class ResourceOwner
{
      Lock         lock; 
      Resource     resource;

      public:
         ResourceHolder<Resource>  getExclusiveAccess()
         {
              // Let the ResourceHolder lock and unlock the lock
              // So while a thread holds a copy of this object only it
              // can access the resource. Once the thread releases all
              // copies then the lock is released allowing another
              // thread to call getExclusiveAccess().
              //
              // Make it behave like a form of smart pointer
              //    1) So you can pass it around.
              //    2) So all properties of the resource are provided via ->
              //    3) So the lock is automatically released when the thread
              //       releases the object.

              return ResourceHolder<Resource>(lock, resource);
         }
};

( , )

template<typename Resource>
class ResourceHolder<
{
    // Use a shared_ptr to hold the scopped lock
    // When first created will lock the lock. When the shared_ptr
    // destroyes the scopped lock (after all copies are gone)
    // this will unlock the lock thus allowding other to use
    // getExclusiveAccess() on the owner
    std::shared_ptr<scopped_lock>    locker;
    Resource&                        resource;   // local reference on the resource.

    public:
        ResourceHolder(Lock& lock, Resource& r)
            : locker(new scopped_lock(lock))
            , resource(r)
        {}

        // Access to the resource via the -> operator
        // Thus allowing you to use all normal functionality of 
        // the resource.
        Resource* operator->() {return &resource;}
};

:

ResourceOwner<list<int>>  lockedList;

void threadedCode()
{
    ResourceHolder<list<int>>  list = lockedList.getExclusiveAccess();

    list->push_back(1);
}
// When list goes out of scope here. 
// It is destroyed and the the member locker will unlock `lock`
// in its destructor thus allowing the next thread to call getExclusiveAccess()
+7

- , , RAII.

class LockedList {
private:
    std::list<MyClass> m_List;
    MutexObject Mutex;
    friend class LockableListLock;
};

class LockableListLock {
private:
    LockedList& list_;
public:
    LockableListLock(LockedList& list) : list_(list) { list.Mutex.Lock(); }
    ~LockableListLock(){ list.Mutex.Unlock(); }
}

LockableList list;
{
    LockableListLock lock(list); // The list is now locked.

    // do stuff to the list

} // The list is automatically unlocked when lock goes out of scope.

, - , std:: list LockableListLock, LockedList LockableListLock. , std:: list:: begin()

std::list::iterator LockableListLock::begin() {
    return list_.m_List.begin();
}

:

LockableList list;
LockableListLock lock(list);
// list.begin();   //This is a compiler error so you can't 
                   //access the list without locking it
lock.begin(); // This gets you the beginning of the list
+6

, , : , , , , , , . , RAII.

locked ( ) , .

// C++ like pesudo-code. Not intended to compile as-is.
struct mutex {
    void lock() { /* ... */ }
    void unlock() { /* ... */ }
};

struct lock {
    lock(mutex &m) { m.lock(); }
    ~lock(mutex &m) { m.unlock(); }
};

template <class container>
class locked {
    typedef container::value_type value_type;
    typedef container::reference_type reference_type;
    // ...

    container c;
    mutex m;
public:
    void push_back(reference_type const t) {
        lock l(m);
        c.push_back(t);
    }

    void push_front(reference_type const t) { 
        lock l(m);
        c.push_front(t);
    }

    // etc.
};

( ) - - , :

std::vector<int> x;

x.push_back(y);

... :

locked<std::vector<int> > x;

x.push_back(y);

, begin(), end(), push_front, push_back .., locked<container> - , , , ...

+2

, LockedList. , , :

, , , ( ).

"" . , , .

+1

, - . , , . , , . , .

, , .

struct ScopedLocker {
  ScopedLocker(MutexObject &mo_) : mo(mo_) { mo.Lock(); }
  ~ScopedLocker() { mo.Unlock(); }

  MutexObject &mo;
};

.

class LockedList {
  private:
    std::list<MyClass> m_List;
    MutexObject Mutex;

  public:
    struct ScopedLocker {
       ScopedLocker(LockedList &ll);
       ~ScopedLocker();
    };
};

, MutexObject.

, , . - . , , , , , .

void foo(LockedList &list) {
  for (size_t i = 0; i < 100000000; i++) {
    list.push_back(i);
  }
}

, , . , . , , . , , .

, , . ++ 11 .

0

(, , , ):

template<class T1, class T2>
class combine : public T1, public T2
{
public:

    /// We always need a virtual destructor.
    virtual ~combine() { }
};

:

    // Combine an std::mutex and std::map<std::string, std::string> into
    // a single instance.
    combine<std::mutex, std::map<std::string, std::string>> mapWithMutex;

    // Lock the map within scope to modify the map in a thread-safe way.
    {
        // Lock the map.
        std::lock_guard<std::mutex> locked(mapWithMutex);

        // Modify the map.
        mapWithMutex["Person 1"] = "Jack";
        mapWithMutex["Person 2"] = "Jill";
    }

std:: recursive_mutex std:: set, .

0

All Articles