Home > C++11 > RaiiGuard

RaiiGuard

In my application domain, the following shared object pattern occurs quite frequently:

sharedobj

Typically, Shared is a small, dynamically changing, in-memory, database and two or more threads need to perform asynchronous CRUD operations on its content. I’ve seen, and have written, code like this more times than I can count:

  Shared so{};
  //The following idiom is exception-unsafe
  so.lock();
  so.doStuff(); //doStuff() better not throw!
  so.unlock();

The problem with the code is that if doStuff() (or any other code invoked within its implementation) throws an exception, then unlock() won’t get executed. Thus, any other thread that attempts to access so by calling so.lock() will get blocked… forever… deadlock city.

Here is one way of achieving exception safety, but it’s clunky:

  Shared so{};
  //Clunky way to achieve exception safety
  so.lock();
  try {
    so.doStuff(); //doStuff() can throw!
  }
  catch(...) {
    so.unlock();
  }
  so.unlock();

I use Stroustrup’s classic RAII (Resource Acquisition Is Initialization) technique to achieve exception safety:

  Shared so{};
  RaiiGuard<Shared> guardedSo{so};
  guardedSo->doStuff(); //It's OK for doStuff() to throw!

Since the solution involves constructing/destructing a RaiiGuard<Shared> object in the same scope as the Shared object it encapsulates and protects from data races, the tradeoff is an exception-safety gain for a bit of performance loss.

Here is the simple RaiiGuard class template that I use to achieve the non-clunky way to exception safety:

template<typename T>
class RaiiGuard {

public:
  explicit RaiiGuard(T& obj) : _obj(obj) {
    _obj.lock();
  }

  T* operator->() {
    return &_obj;
  }

  ~RaiiGuard() {
    _obj.unlock();
  }

private:
  T& _obj;

};

It locks access to an object of type T on construction and unlocks it on destruction – the definition of RAII and RRID (Resource Release Is Destruction). The user obtains access to the “monitored” underlying object by invoking T* RaiiGuard<T>::operator->().

The only requirement imposed on the template type T is that it implements the lock() and unlock() functions as follows:


class T {

public:

  void lock() {
    m.lock();
  }

  void unlock() {
    m.unlock();
  }

  void doStuff() {
  }

private:

  mutable std::mutex m;

};

Since they all contain lock()/unlock() pairs, any of the STL mutex types can be substituted for std::mutex.

How do you achieve exception safety for the shared object pattern?

Categories: C++11 Tags: , ,
  1. February 6, 2016 at 5:15 am

    A couple decades ago I would have agreed that was the solution. I changed my mind after realizing that you very rarely need locks and the typical example scenario is a paper tiger. If you assume you have simultaneous writers (readers don’t need locks just consistency, i.e. what is the state of the world now not will it be after the current write).
    A simple generic non locking replacement implementation without locks is to make the access to a shared resource (memory) a thread with a thread safe queue (hopefully non-blocking for reads and writes). Start the thread add the access to the shared resource(memory) to the queue the reference to the function or method or an object with a standard method or even DSL statement/function. Threaded shared object processes all requests priority FIFO or even writes first or reads first depending on the use case in a loop that sleeps and checks or use a function wrapper that starts the thread on add to queue and maybe even have the thread stop on empty queue.
    Just my horse sense.

  2. February 6, 2016 at 9:03 pm

    Nice post again, Tony. The idea is so great that it has been standardized as std::lock_guard: http://en.cppreference.com/w/cpp/thread/lock_guard

    I believe this just works with your custom type T:

    {
    std::lock_guard m(t);
    // …
    }

    There’s also unique_lock, which is movable.

    • February 7, 2016 at 9:01 am

      Thanks for stopping by and commenting Herb. I’m a huge fan!

  1. No trackbacks yet.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.