The Lazy Programmer

August 10, 2008

Adding a lock() statement to C++

Filed under: C#,Multi-threading,Programming — ferruccio @ 9:43 am
Tags: , ,

One of the things I like about C# that I miss in C++ is the lock() statement. It provides a simple way to control access to an object across multiple threads.

When I first started writing Windows apps, I used the Windows critical section API directly. After getting tired of EnterCriticalSection/LeaveCriticalSection, (I suspect like everybody else) I created a CriticalSection class that was a simple wrapper around the raw API.

Eventually, I got around to using mutexes from the Boost Thread class. You can create a scope guard whose destructor releases its referenced object, but it still lacks the simple elegance and clarity of being able to say:

lock (some-object) {
    do something to some-object
}

My initial plan was to pass the address of the object to be locked to a function which would then maintain a map of object addresses to mutexes. It didn’t take to long to see that idea was a real turkey for several reasons.

  • I didn’t like the overhead of a lookup on every lock and unlock.
  • The map will just keep growing. There’s no way to tell when an object has gone out of scope.
  • If the object is on the stack, its address could conflict with another object that that was locked and happened to occupy that same address previously.
  • Its neither type-safe nor exception-safe.

So I went back to the drawing board. I decided that instead of being able to lock any arbitrary object, an acceptable compromise would be to have to explicitly mark objects as lockable.

The first thing we do is create a synchronized<> template used to create lockable objects:

#include <boost/thread.hpp>

template <class T>
class synchronized : public T, public boost::mutex {
public :
    synchronized<T>() : T(), boost::mutex() {}
};

A synchronized object is a child both of the class of objects we wish to lock and a boost::mutex object. I know some people don’t like multiple inheritance so just think of the inheritance from T as an interface.

If you don’t want to use boost, you can always inherit from that CriticalSection class that you wrote years ago 🙂

Now when we want access to an object to be synchronized we can simply say:

synchronized<MyClass> myObject;

You can call lock() and unlock methods on myObject, but that really didn’t buy us much, did it?

The next step is to create a helper class that will be used to lock and unlock objects directly.

class _locker {
public:
    _locker(boost::mutex& m) : m_(m) { m_.lock(); run_ = true; }
    _locker(boost::mutex& m, int) : m_(m) { run_ = m_.try_lock(); }
    ~_locker() { m_.unlock(); }
    bool _run() { return run_; }
    void _stop() { run_ = false; }
private:
    boost::mutex&   m_;
    bool            run_;
};

Since synchronized is a subclass of boost::mutex, you can now say:

synchronized<MyClass> myObject;
_locker myLock(myObject);
myLock.lock();

Now, when myLock goes out of scope, myObject will automatically be unlocked. Finally, we define a couple of macros to support the final piece of the puzzle:

#define LOCK(x) for (_locker _lock(x); _lock._run(); _lock._stop())
#define TRY_LOCK(x) for (_locker _lock(x, 0); _lock._run(); _lock._stop())

We can finally say:

LOCK (myObject) {
    do something with myObject
}

Let’s try a more concrete example.

#include <list>
using namespace std;

class MyClass {
public :
    MyClass() { value = 0; }

    void set(int v) { value = v; }
    int get() { return value; }

private :
    int value;
};

int main(int argc, char** argv) {
    synchronized<MyClass> mc;
    LOCK (mc) {
        mc.set(10);
        int n = mc.get();
    }

    bool locked = false;
    TRY_LOCK(mc) {
        locked = true;
    }
    std::cout << "locked: " << locked << endl;

    synchronized<list<int>> sli;
    LOCK (sli) {
        sli.push_back(1);
        sli.push_back(2);
    }

    return 0;
}

Note: the code in the TRY_LOCK() block executes only if the lock was successful.

Using LOCK() is exception-safe. If an exception is thrown inside the LOCK block, the object will be unlocked. You can use break or goto to exit the block and the object will be unlocked. You can’t, however, do something like:
synchronized<int> i;

Since ints aren’t class objects and cannot be subclassed. Personally, I don’t find this to be a problem, but if you really want to be able to do this you can create a “boxing” template to help you along:

template <class T>
struct box {
    T object;
};

Then you could use it like:

synchronized<box<int>> si;
LOCK (si) {
    si.object = 0;
}

I think you lose some readability that way.

Advertisements

8 Comments

  1. Neat trick!

    To solve the primitive types issue, how about something like this (I hope my angle brackets make it through the commenting system…):

    template
    class synchronized :
        public synchronized_impl<T, boost::is_scalar::value>
    {
        private:
            typedef synchronized_impl<T, boost::is_scalar::value> base;
        public:
            synchronized() : base(T()) { }
            synchronized(const T &x) : base(x) { }
    };
    
    template
    class synchronized_impl : public T, public boost::mutex
    {
        public:
            synchronized_impl(const T &x) : T(x), boost::mutex() { }
    };
    
    template
    class synchronized_impl : public boost::mutex
    {
        // specialization for primitive types
        public:
            synchronized_impl(const T &x) : T(x), boost::mutex() { }
            operator T &() { return x_; }
            operator const T &() const { return x_; }
            synchronized_impl &operator= (T x) { x_ = x; return *this; }
    
        private:
            T x_;
    };

    Comment by Edd — August 11, 2008 @ 2:36 pm

  2. I was afraid of that 🙂 Let me try again:

    template<typename T>
    class synchronized :
        public synchronized_impl<T, boost::is_scalar<T>::value>
    {
        private:
            typedef synchronized_impl<T, boost::is_scalar<T>::value> base;
        public:
            synchronized() : base(T()) { }
            synchronized(const T &x) : base(x) { }
    };
    
    template<typename T, bool>
    class synchronized_impl : public T, public boost::mutex
    {
        // as before
        public:
            synchronized_impl(const T &x) : T(x), boost::mutex() { }
    };
    
    template<typename T>
    class synchronized_impl<T, true> : public boost::mutex
    {
        // specialization for primitive types
        public:
            synchronized_impl(const T &x) : T(x), boost::mutex() { }
            operator T &() { return x_; }
            operator const T &() const { return x_; }
            synchronized_impl &operator= (T x) { x_ = x; return *this; }
    
        private:
            T x_;
    };

    Comment by Edd — August 11, 2008 @ 2:39 pm

  3. Nice idea, Edd! However, I couldn’t get it to work. The code needed only minimal tweaking to compile, but as soon as I tried assigning to a synchronized<int>, I got a compiler error. I suspect the problem stems from the fact that boost::mutex inherits from boost::noncopyable.
    I thought of moving the mutex inside the synchronized class instead of inheriting from it, but I like the idea of synchronized objects not being copyable.

    Comment by Ferruccio — August 11, 2008 @ 8:09 pm

  4. That’ll teach me to post code without trying to compile. Here’s a fixed version:

    #include <boost/type_traits/is_scalar.hpp>
    #include <boost/thread/mutex.hpp>
    #include <iostream>
    
    template<typename T, bool>
    class synchronized_impl : public T
    {
        public:
            synchronized_impl(const T &x) : T(x) { }
    };
    
    template<typename T>
    class synchronized_impl<T, true>
    {
        public:
            synchronized_impl(const T &x) : x_(x) { }
            operator T &() { return x_; }
            operator const T &() const { return x_; }
            synchronized_impl &operator= (T x) { x_ = x; return *this; }
    
        private:
            T x_;
    };
    
    template<typename T>
    class synchronized :
        public synchronized_impl<T, boost::is_scalar<T>::value>, boost::mutex
    {
        private:
            typedef synchronized_impl<T, boost::is_scalar<T>::value> base;
    
        public:
            synchronized() : base(T()), boost::mutex() { }
            explicit synchronized(const T &x) : base(x), boost::mutex() { }
    
            synchronized &operator= (const T &rhs)
            {
                base &b = *this;
                b = rhs;
                return *this;
            }
    };
    
    int main()
    {
        synchronized<int> i;
        i = 5;
        int &i2 = i;
        std::cout << i2 << '\n';
        return 0;
    }
    

    Comment by Edd — August 12, 2008 @ 3:34 am

  5. Hmm.. the trick is almost 13 years old. ACE had this technique LOOONG back. Not sure what is different here?

    Comment by Bala Natarajan — August 14, 2008 @ 2:40 am

  6. Nice trick
    Bookmarked

    Comment by bull — December 15, 2008 @ 10:13 pm

  7. A gem. I’m going to try to incorporate this into my projects. Going back to C after working with more evolved languages is painful; this will make it less so.

    B. Natarajan, yes, we’re all proud of you for having seen this technique before. But ACE is not widely used, compared to say, Boost. While it’s an interesting footnote that ACE incorporates this feature (I’m assuming it does), it doesn’t diminish the usefulness of this blog post. First, others like myself may be discovering the technique for the first time through this page. Second, this describes how to implement in a standalone fashion without needing to strip it from the rest of the baggage in the ACE framework.

    Comment by cag — August 18, 2009 @ 9:28 pm

  8. One minor issue — if the class of the object you are locking does not have virtual destructor, you could run into some problems if anyone allocates a synchronized with new, stores the result in a T* then deletes the T*.

    I admit that would be abnormal usage for this paradigm, but it is a concern and not something you can really defend against, unless you have type traits like has_virtual_destructor

    Comment by SimonRev — September 14, 2009 @ 4:59 pm


RSS feed for comments on this post.

Create a free website or blog at WordPress.com.

%d bloggers like this: