tlock : Any C++ object read/write thread-safe provider

0
52

Introduction

Some times you need an object to be accessible from many threads, either read only (so all threads can access), or write (so only one thread can access).

The implementation is based on: 

Using the code

You need a proxy class so, when an object’s method is called, the lock/unlock mechanism will be compiled automatically:

    class proxy 
                {
                T *const p;
                RWMUTEX* m;
                int me;                 public:
                    proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m), me(_me) 
  { 
  if (me == 2) 
  m->LockWrite(); 
  else 
  m->LockRead(); 
  }
                    ~proxy() 
  { 
  if (me == 2) 
  m->ReleaseWrite(); 
  else  
  m->ReleaseRead(); 
  }
                    T* operator -> () { return p; }
                    const T* operator -> () const { return p; }
                };

The constructor and destructor of this class do all the work. They lock with RWMutex before the object method is to be called, and they unlock after the method has been called.

The tlock class will then look like this:

template <typename T> class tlock
 {
 private:
 mutable T t;
 mutable RWMUTEX m;

 class proxy 
 {
 T *const p;
 RWMUTEX* m;
 int me;
 public:
 proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m), me(_me) { if (me == 2) m->LockWrite(); else m->LockRead(); }
 ~proxy() { if (me == 2) m->ReleaseWrite(); else m->ReleaseRead(); }
 T* operator -> () { return p; }
 const T* operator -> () const { return p; }
 };

 public:
 template< typename ...Args>
 tlock(Args ... args) : t(args...) {}
 const proxy r() const
 {
 return proxy(&t,&m,1);
 }
 proxy w()
 {
 return proxy(&t, &m, 2);
 }

 proxy operator -> () { return w(); }
 const proxy operator -> () const { return r(); }

 };

The r() method is called when you want read-only access to the object. This is the default when operator -> is called on a const object.

The w() method is called when you want write access to the object. This is the default for operator -> if the object is not constant.

Let’s see some incorrect usage (without tlock):

vector<int> s;
std::thread t1([&]() { s.push_back(0); });
std::thread t2([&]() { s.push_back(1); });
std::thread t3([&]() { s.push_back(2); });
std::thread t4([&]() { s.push_back(3); });
std::thread t5([&]() { s.push_back(4); });
t1.join();t2.join(); t3.join(); t4.join(); t5.join();

Boom.  

And now, the correct usage:

UWL::tlock<vector<int>> s;
std::thread t1([&]() { s.w()->push_back(0); });
std::thread t2([&]() { s.w()->push_back(1); });
std::thread t3([&]() { s.w()->push_back(2); });
std::thread t4([&]() { s.w()->push_back(3); });
std::thread t5([&]() { s.w()->push_back(4); });
t1.join();t2.join(); t3.join(); t4.join(); t5.join();

Now the writing is thread safe.

History

12 – 05 – 2017: First release

LEAVE A REPLY