25

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.

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
dario_ramos
  • 7,118
  • 9
  • 61
  • 108
  • 2
    It's hard for me to imagine doing this with less code. The code required for your proposed solution is one line for the class (the mutex member) plus one line for each function. I don't think it's going to get smaller without specific language support. Is this your question? If so, the answer is "C++ does not have language support for making monitors". :) – Magnus Hoff Sep 28 '12 at 21:01
  • 2
    Why don't you just use a mutex, it is widely available. Inventing your own synchronization primitives has a knack for byting badly. – Hans Passant Sep 28 '12 at 21:02
  • See [Boost.Thread](http://www.boost.org/libs/thread/) in case that wasn't sufficiently obvious. :-] – ildjarn Sep 28 '12 at 21:03

2 Answers2

24

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;
Mike Vine
  • 9,468
  • 25
  • 44
  • 2
    I find it pretty hard to believe that `threadSafeVector->push_back(0);` works even though internally, we are calling `operator->` twice which makes me think that it should have worked like this: `threadSafeVector->->push_back(0);`. The first `threadSafeVector->` returns a monitor_helper and then second `->` calls operator-> on this monitor helper. – pranavk Oct 17 '18 at 04:43
  • Also, I wonder why not just use lock_guard/scope_guard in `monitor::operator->` and return a `&m_cl` from there. Is there a reason you introduced that additional struct? – pranavk Oct 17 '18 at 04:49
  • 6
    @pranavk The arrow operator in c++ has special semantics allowing it to be repeatedly applied when the return type of the previous call is a pointer which this answer uses. See for example [this](https://stackoverflow.com/questions/10677804/how-arrow-operator-overloading-works-internally-in-c) for details. As to your second point, we need to keep a lock for the duration of the function call which is the lifetime of the special `monitor_helper` struct. Thats not possible with a normal lock. – Mike Vine Oct 17 '18 at 08:41
  • Thanks for link to drill-down behavior. I didn't know about it. As to the second part, having a `lock_guard` as first line of `monitor::operator->` will do what you say - keeping a lock for the duration of the function call. – pranavk Oct 17 '18 at 15:57
  • 3
    @pranavk. No it wont. Try it - setup a debugger and put a break point in the destructor of the `lock_guard` and the function-call . It will take the lock for as long as it takes to return the pointer and then unlock it _before_ it calls the function. This uses the fact that a temp object is destroyed at the end of the full expression _after_ the function call (which then unlocks the lock). – Mike Vine Oct 18 '18 at 07:29
  • Worth noting that this only works because unique_lock has a move constructor and not a default copy constructor, if you are not using unique_lock then you need to write a copy constructor / move constructor inside the monitor_helper to transfer ownership. Otherwise it may appear to work but could fail if RVO is not performed by the compiler when returning the monitor_helper object (e.g with gcc -fno-elide-constructors) so beware. – Spacen Jasset Mar 17 '20 at 09:56
13

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 {};
};
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • This is a very elegant solution; the only downside is that it requires variadic templates support, which VC++ still lacks. – dario_ramos Oct 23 '12 at 15:03
  • 1
    @dario_ramos Once upon a time C++ had no variadic templates ;) You can use overridden methods: `operation` with 0,1,2,3... arguments instead of just one `operation` with any number of arguments. Assuming noone uses more than 10 arguments it is possible to implement - however more troublesome than variadic templates... – PiotrNycz Oct 23 '12 at 18:49
  • That's a very neat solution. I don't suppose you can recommend a book or article that could explain the use of the variadic templates a bit more? I find the struct FunctionType hard to fathom (particularly the Object::* bit.) – Tom Davies Nov 05 '13 at 21:42
  • @TomDavies: There are 3 things behind this solution: variadic templates http://en.wikipedia.org/wiki/Variadic_template; pointer to member function: http://www.parashift.com/c++-faq/fnptr-vs-memfnptr-types.html; and function type as template parameter: http://stackoverflow.com/a/4642127/1463922... I cannot recommend any books since I am just using C++ standard and internet search if I need some information, so maybe search SO and if you did not find useful answers, ask for books good for this topics... – PiotrNycz Nov 06 '13 at 15:12
  • 4
    I pity any non c++ programmer stumbling randomly upon this question. – UmNyobe Jun 03 '16 at 16:09
  • @UmNyobe quite. I stumbled across this answer whilst looking for a `operator->` version and your comment drove me to write what I hope is a much easier to use version. – Mike Vine Jan 23 '18 at 19:41