Compile time guaranteed thread synchronization in C++
Written by P. Most   

The usual way to synchronize thread access to a shared resource, is by convention or agreement through comments in the source code. The obvious risk is of course that you might forget or overlook this important hint, with the usual consequences of a corrupt or invalid data state. So how can we make sure that accessing a shared resource is synchronized?

Content:

C++

Part 1 - The Principle

Part 2 - Production Ready

Part 3 - Polished Finish




Part 1 - The Principle


The solution is actually quite simple. When we don't want anybody to access the attributes of our class, we declare them private. To restrict or control the access to shared data we embed it into a class of it's own like this:

#include <boost/thread/mutex.hpp>

using namespace boost;

template < typename Resource >
	class ThreadResourceLock {
		public:
			Resource *lock( void )
			{
				my_mutex.lock();

				return ( &my_resource );
			}

			void unlock( Resource *resource )
			{
				if ( &my_resource == resource )
					my_mutex.unlock();
			}

		private:
			mutex my_mutex;
			Resource my_resource;
	}; 

Now every method which wants to access the resource has to call the lock() method:

#include <list>
#include <string>
#include "threadresourcelock.hpp"

using namespace std;

class StringQueue {
	public:
		void append( const string &s )
		{
			list< string > *strings = my_strings.lock();
			strings->push_back( s );
			my_strings.unlock( strings );
		}

		void prepend( const string &s )
		{
			list< string > *strings = my_strings.lock();
			strings->push_front( s );
			my_strings.unlock( strings );
		}

	private:
		ThreadResourceLock< list< string > > my_strings;
};

It's perfectly save now for two threads to call append or prepend and if you add some new methods, for example to extract the strings from the queue, then you can't forget to synchronize the access to it.

This is of course just the beginning and we could make it even more convenient if we introduced some kind of smart pointers which would allow us to use RAII to lock and unlock the resource, but I should note (and because my former colleague Ralf Holly reminded me), that I left out some small, but none the less, important details for brevity, namely:

  • Copy constructor and assignment operator should be private.
  • Const correctness.
  • Nullify the resource pointer in unlock.
  • Might be better to throw an exception when trying to unlock the wrong resource.
  • How to construct a resource if the default constructor isn't available.


But I will address these in part 2.




Part 2 - Production Ready


As mentioned in part 1, there are some important things missing. So without further ado, here is the complete class:

#include <boost/thread/mutex.hpp>
#include <boost/noncopyable.hpp>
#include <boost/assert.hpp>

template < typename Resource >
	class ThreadResourceLock : boost::noncopyable {
		public:
			Resource *lock( void )
			{
				my_mutex.lock();

				return ( &my_resource );
			}

			void unlock( Resource **resource )
			{
				BOOST_ASSERT( &my_resource == *resource );

				if ( &my_resource == *resource ) {
					*resource = NULL;
					my_mutex.unlock();
				}
			}

			const Resource *lock( void ) const
			{
				my_mutex.lock();

				return ( &my_resource );
			}

			void unlock( const Resource **resource ) const
			{
				BOOST_ASSERT( &my_resource == *resource );

				if ( &my_resource == *resource ) {
					*resource = NULL;
					my_mutex.unlock();
				}
			}
				
		private:
			mutable boost::mutex my_mutex;
			Resource my_resource;
	};

Some things to note:

  • I decided not to throw an exception but to use the BOOST_ASSERT() when the user tries to unlock the wrong resource pointer.
  • To nullify the resource pointer I'm using a pointer to pointer instead of a reference to a pointer, because then in the source code it is more obvious from the call that the resource pointer will be manipulated.


The corresponding StringQueue sample looks like this now:

#include <list>
#include <string>
#include "threadresourcelock.hpp"

using namespace std;

class StringQueue {
	public:
		void append( const string &s )
		{
			list< string > *strings = my_strings.lock();
			strings->push_back( s );
			my_strings.unlock( &strings );
		}

		void prepend( const string &s )
		{
			list< string > *strings = my_strings.lock();
			strings->push_front( s );
			my_strings.unlock( &strings );
		}

		size_t size() const
		{
			const list< string > *strings = my_strings.lock();
			size_t n = strings->size();
			my_strings.unlock( &strings );

			return ( n );
		}

	private:
		ThreadResourceLock< list< string > > my_strings;
};

The problem of how to deal with resources which don't provide a default constructor still exists, but should be easily solvable by deriving from the resource (std::list<> in my example) and provide a default constructor and use that as the actual resource i.e.:

class string_list : public list< string > {
	public:
		string_list()
			: list< string >( 30 )
		{
		}
};

That's it for part 2. In part 3 I will show how we can use a smart pointer to make it even more convenient and more importantly exception safe.

Update:

Ralf had (again) a couple of valuable remarks:

  1. The current lock/unlock pairs violate the DRY principle.
  2. The used boost::mutex can't handle recursion.

So in the following source code I corrected these points:

  1. The const versions of lock/unlock now call the non-const versions.
  2. The ThreadResourceLock has an additional template parameter which allows the user to define what mutex to use.

I also fixed an error in the above StringQueue::size which nobody of my readers detected, namely the return statement was missing!

#include <boost/thread/recursive_mutex.hpp>
#include <boost/noncopyable.hpp>
#include <boost/assert.hpp>

template < typename Resource, typename Mutex = boost::recursive_mutex >
	class ThreadResourceLock : private boost::noncopyable {
		public:
			Resource *lock( void )
			{
				my_mutex.lock();

				return ( &my_resource );
			}

			void unlock( Resource **resource )
			{
				BOOST_ASSERT( &my_resource == *resource );

				if ( &my_resource == *resource ) {
					*resource = NULL;
					my_mutex.unlock();
				}
			}

			const Resource *lock( void ) const
			{
				return ( const_cast< ThreadResourceLock< Resource > * >
					( this )->lock() );
			}

			void unlock( const Resource **resource ) const
			{
				const_cast< ThreadResourceLock< Resource > * >
					( this )->unlock( const_cast< Resource ** >( resource ));
			}
		private:
			mutable Mutex my_mutex;
			Resource my_resource;
	};




Part 3 - Polished Finish

The current solution works quite well, but is getting quite cumbersome when we want to make it exception safe. If we assume that each append or prepend could run out of memory and throws an exception, we have to make sure that the mutex is getting unlocked and we would have to write something like this:

class StringQueue {
	public:
		void append( const string &s )
		{
			string_list *strings;
			try {
				strings = my_strings.lock();
				strings->push_back( s );
				my_strings.unlock( &strings );
			}
			catch ( ... ) {
				my_strings.unlock( &strings );
				throw;
			}
		}

		void prepend( const string &s )
		{
			string_list *strings;
			try {
				strings = my_strings.lock();
				strings->push_front( s );
				my_strings.unlock( &strings );
			}
			catch ( ... ) {
				my_strings.unlock( &strings );
				throw;
			}
		}

		size_t size() const
		{
			const string_list *strings = my_strings.lock();
			size_t n = strings->size();
			my_strings.unlock( &strings );

			return ( n );
		}

	private:
		typedef list< string > string_list;
		ThreadResourceLock< string_list > my_strings;
};

Not only is this an inelegant solution, but we also run the risk again that the next developer who is adding a new method, is not that thorough. We can do much better when we automate the lock/unlock with a smart pointer like boosts shared_ptr<>.

A little known feature of shared_ptr<> is its ability to accept a destructor functor in the constructor. This allows us to customize what the destructor of shared_ptr<> does to cleanup the pointer it holds and in our case we specify the unlock method. So our ThreadResourceLock looks like this then:

#include <boost/thread/recursive_mutex.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/assert.hpp>
#include <boost/bind.hpp>

template < typename Resource, typename Mutex = boost::recursive_mutex >
	class ThreadResourceLock : private boost::noncopyable {
		public:
			typedef boost::shared_ptr< Resource > Pointer;
			typedef boost::shared_ptr< const Resource > ConstPointer;

			Pointer lock( void )
			{
				my_mutex.lock();

				void ( ThreadResourceLock< Resource >::*unlockMethod )( Resource * ) =
					&ThreadResourceLock< Resource >::unlock;
				Pointer pointer( &my_resource, boost::bind( unlockMethod, this, _1 ));

				return ( pointer );
			}

			void unlock( Resource *resource )
			{
				BOOST_ASSERT( &my_resource == resource );

				my_mutex.unlock();
			}
				
			ConstPointer lock( void ) const
			{
				my_mutex.lock();

				void ( ThreadResourceLock< Resource >::*unlockMethod )( const Resource * ) const =
					&ThreadResourceLock< Resource >::unlock;
				ConstPointer pointer( &my_resource, boost::bind( unlockMethod, this, _1 ));

				return ( pointer );
			}

			void unlock( const Resource *resource ) const
			{
				BOOST_ASSERT( &my_resource == resource );

				my_mutex.unlock();
			}

		private:
			mutable Mutex my_mutex;
			Resource my_resource;
	};

The corresponding StringQueue example looks like this now:

#include <list>
#include <string>

using namespace std;

class StringQueue {
	public:
		void append( const string &s )
		{
			StringListLock::Pointer strings( my_strings.lock() );
			strings->push_back( s );
		}

		void prepend( const string &s )
		{
			StringListLock::Pointer strings( my_strings.lock() );
			strings->push_front( s );
		}

		size_t size() const
		{
			StringListLock::ConstPointer strings( my_strings.lock() );
			size_t n = strings->size();

			return ( n );
		}

	private:
		typedef list< string > string_list;
		typedef ThreadResourceLock< string_list > StringListLock;
		ThreadResourceLock< string_list > my_strings;
};

A very important consequence of this design is that even one liner work correctly and we could therefore make it even more compact:

class StringQueue {
	public:
		void append( const string &s )
		{
			my_strings.lock()->push_back( s );
		}

		void prepend( const string &s )
		{
			my_strings.lock()->push_front( s );
		}

		size_t size() const
		{
			return ( my_strings.lock()->size() );
		}

	private:
		typedef list< string > string_list;
		ThreadResourceLock< string_list > my_strings;
};

If you like to use it in your own project then you can download the header PERASoftwareSolutions_ThreadResourceLock.hpp from here.


Supplemental:

A colleague of mine mentioned that Herb Sutter wrote an article "Associate Mutexes with Data to Prevent Races" for the Dr. Dobbs journal in which he describes another way to ensure proper thread synchronization.




Last Updated on Tuesday, 30 August 2011 18:16
 

Comments  

 
#1 Comment to example of Part IIAndy 2010-01-02 15:49
Hi Peter,
interesting article. I think you have a small copy & paste error and forgot removing the old Methods of part one in your example part two. As a result of this you have defined the lock and unlock methods two times in your example part two.

greetings,
Andy
Quote
 
 
#2 RE: Compile time guaranteed thread synchronizationP. Most 2010-01-02 16:02
Quoting Andy:
Hi Peter,
interesting article. I think you have a small copy & paste error and forgot removing the old Methods of part one in your example part two. As a result of this you have defined the lock and unlock methods two times in your example part two.

greetings,
Andy

My dear non-c++ developing friend please note the 'const' behind the second set of lock/unlock. These will be called when you declare your resource const.
Quote
 

Add comment


Security code
Refresh

PERA Software Solutions GmbH, Powered by Joomla! and designed by SiteGround web hosting