Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a C++ class a Monitor (in the concurrent sense)

Tags:

I want to ensure that only one thread at a time can run a method of my C++ class. In other words, make the class behave like a Monitor.

Is there a pattern, templatized way to do this, or some Boost class I can use? Because my only idea so far is adding a Critical Section member, and acquire it at the beginning of each method and release it at the end (using RAII, of course). But that seems very redundant, and I can't reuse it for some other class.

like image 930
dario_ramos Avatar asked Sep 28 '12 20:09

dario_ramos


People also ask

What is a monitor in concurrent programming?

In concurrent programming, a monitor is a synchronization construct that allows threads to have both mutual exclusion and the ability to wait (block) for a certain condition to become false. Monitors also have a mechanism for signaling other threads that their condition has been met.

What is synchronization monitor Java?

Monitor in Java Concurrency is a synchronization mechanism that provides the fundamental requirements of multithreading namely mutual exclusion between various threads and cooperation among threads working at common tasks. Monitors basically 'monitor' the access control of shared resources and objects among threads.

Which statement best defines a monitor in Java?

A monitor is mechanism to control concurrent access to an object.


2 Answers

You can achieve this with some judicious use of operator-> and modern c++ which gives for much cleaner syntax than the previously accepted answer:

template<class T> class monitor { public:     template<typename ...Args>     monitor(Args&&... args) : m_cl(std::forward<Args>(args)...){}      struct monitor_helper     {         monitor_helper(monitor* mon) : m_mon(mon), m_ul(mon->m_lock) {}         T* operator->() { return &m_mon->m_cl;}         monitor* m_mon;         std::unique_lock<std::mutex> m_ul;     };      monitor_helper operator->() { return monitor_helper(this); }     monitor_helper ManuallyLock() { return monitor_helper(this); }     T& GetThreadUnsafeAccess() { return m_cl; }  private:     T           m_cl;     std::mutex  m_lock; }; 

The idea is that you use the arrow operator to access the underlying object, but that returns a helper object which locks and then unlocks the mutex around your function call. Then through the magic of the language repeatedly applying operator-> you get a reference to the underlying object.

Usage:

monitor<std::vector<int>> threadSafeVector {5};  threadSafeVector->push_back(0); threadSafeVector->push_back(1); threadSafeVector->push_back(2);  // Create a bunch of threads that hammer the vector std::vector<std::thread> threads; for(int i=0; i<16; ++i) {     threads.push_back(std::thread([&]()     {         for(int i=0; i<1024; ++i)         {             threadSafeVector->push_back(i);         }     })); }  // You can explicitely take a lock then call multiple functions // without the overhead of a relock each time. The 'lock handle' // destructor will unlock the lock correctly. This is necessary // if you want a chain of logically connected operations  {     auto lockedHandle = threadSafeVector.ManuallyLock();     if(!lockedHandle->empty())     {         lockedHandle->pop_back();         lockedHandle->push_back(-3);     } }  for(auto& t : threads) {     t.join(); }  // And finally access the underlying object in a raw fashion without a lock // Use with Caution!  std::vector<int>& rawVector = threadSafeVector.GetThreadUnsafeAccess(); rawVector.push_back(555);  // Should be 16393 (5+3+16*1024+1) std::cout << threadSafeVector->size() << std::endl; 
like image 177
Mike Vine Avatar answered Nov 21 '22 17:11

Mike Vine


First make generic monitor class. With power of C++11 you can do it as simple as this:

template <class F> struct FunctionType; template <class R, class Object, class... Args> struct FunctionType<R (Object::*)(Args...)> {   typedef R return_type; }; template <class R, class Object, class... Args> struct FunctionType<R (Object::*)(Args...) const> {   typedef R return_type; };  template <class Object_> class Monitor { public:    typedef Object_ object_type;    template <class F, class... Args >    typename FunctionType<F>::return_type operation(const F& f, Args... args)    {        critical_section cs;        return (object.*f)(args...);    }    template <class F, class... Args >    typename FunctionType<F>::return_type operation(const F& f, Args... args) const    {        critical_section cs;        return (object.*f)(args...);    } private:   object_type object;   class critical_section {}; }; 

Of course critical_section implementation is up to you. I recommend POSIX or some BOOST.

It is ready to use right now:

Monitor<std::vector<int> > v; v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 1); v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 2); size = v.operation(&std::vector<int>::size); std::cout << size << std::endl; 

As you can see sometimes you'll need to explicitly state which member function you want to call - std::vector<> has more than one push_back...


For compilers which still do not support variadic template - the solution without it below - I have time for up to two arguments - it is very inconvenient - if required - add function with more arguments:

template <class F> struct FunctionType; template <class R, class Object> struct FunctionType<R (Object::*)()> {   typedef R return_type; }; template <class R, class Object> struct FunctionType<R (Object::*)() const> {   typedef R return_type; }; template <class R, class Object, class Arg1> struct FunctionType<R (Object::*)(Arg1)> {   typedef R return_type; }; template <class R, class Object, class Arg1> struct FunctionType<R (Object::*)(Arg1) const> {   typedef R return_type; }; template <class R, class Object, class Arg1, class Arg2> struct FunctionType<R (Object::*)(Arg1,Arg2)> {   typedef R return_type; }; template <class R, class Object, class Arg1, class Arg2> struct FunctionType<R (Object::*)(Arg1,Arg2) const> {   typedef R return_type; };  template <class Object_> class Monitor { public:    typedef Object_ object_type;    template <class F>    typename FunctionType<F>::return_type operation(const F& f)    {        critical_section cs;        return (object.*f)();    }    template <class F>    typename FunctionType<F>::return_type operation(const F& f) const    {        critical_section cs;        return (object.*f)();    }    template <class F, class Arg1>    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1)    {        critical_section cs;        return (object.*f)(arg1);    }    template <class F, class Arg1>    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1) const    {        critical_section cs;        return (object.*f)(arg1);    }    template <class F, class Arg1, class Arg2>    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2)    {        critical_section cs;        return (object.*f)(arg1, arg2);    }    template <class F, class Arg1, class Arg2>    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2) const    {        critical_section cs;        return (object.*f)(arg1, arg2);    } private:   object_type object;   class critical_section {}; }; 
like image 40
PiotrNycz Avatar answered Nov 21 '22 15:11

PiotrNycz