Automation
The pattern we've presented is sound, but as we apply it to the shared data in our system we'll find that some parts of the pattern are repetitive (e.g., the lock/try_lock/unlock code). It would be nice to automate the boring parts. Here's one way.
First, an initial optional step: We might need to make our mutex testable if it isn't already. Many mutex services (including C++0x std::mutex) don't provide a way to ask, "Do I already hold a lock on this mutex?" But we need a method like is_held to be available. Fortunately, we can easily get it by wrapping together an arbitrary mutex type with a thread ID that stores the ID of the thread that currently holds the mutex. The thread ID initially holds an invalid value that means no threads own the mutex. When we acquire a lock, we set the ID; and when we release a lock, we reset it back to its default nobody-loves-me state:
template<typename Mutex>
class <b>TestableMutex {</b>
public:
void lock() { m.lock(); id = this_thread::get_id(); }
void unlock() { id = 0; m.unlock(); }
bool try_lock() { bool b = m.try_lock();
if( b ) id = this_thread::get_id();
return b; }
<b>bool is_held() { return id == this_thread::get_id(); }</b>
private:
Mutex m;
<b>atomic<thread::id> id;</b>
// for recursive mutexes, add a count
};
Now we can automate the boring parts of the mutex association pattern. For example, given the following convenience macros:
#define <b>PROTECTED_WITH(MutType)</b> \
public: void lock() { mut_.lock(); } \
public: bool try_lock() { return mut_.try_lock(); } \
public: void unlock() { mut_.unlock(); } \
private: TestableMutex<MutType> mut_;
#define <b>PROTECTED_MEMBER(Type,name)</b> \
public: Type& name() { assert(mut_.is_held()); return name##_; } \
private: Type name##_;
Now we associate data with a mutex more easily:
// Example 2: Example 1 rewritten using the convenience macros.
//
struct MyData {
PROTECTED_WITH( some_mutex_type );
PROTECTED_MEMBER( vector<int>, v );
PROTECTED_MEMBER( Widget*, w );
};
The above is identical to the longer version we wrote out by hand in Example 1. If you don't like macros, or don't have them in your language, you can use other methods to get a similar effect.
Granular Control
But what if we already have struct or data that naturally goes together, but different groups of members are covered by different mutexes? To express that, first just define each group on its own, either by hand as in Example 1 or by using a convenience library as in Example 2. For convenience, I'll use the latter:
struct Group1 {
PROTECTED_WITH( some_mutex_type );
PROTECTED_MEMBER( vector<int>, v );
PROTECTED_MEMBER( Widget*, w );
};
struct Group2 {
PROTECTED_WITH( some_mutex_type );
PROTECTED_MEMBER( (map<string,list<int>>), map_from_2 );
};
struct Group3 {
PROTECTED_WITH( some_other_mutex_type );
PROTECTED_MEMBER( complex<float>, cf );
};
Then combine them:
struct MyData { Group1 group1; Group2 group2; Group3 group3; };
// Sample use
{
lock_guard<Group1> lock( data.group1 );
data.group1.v().push_back( 100 );
}
Summary
In code, associate mutexes with the data they control. Leverage this to find latent race conditions automatically and deterministically at test time based on code coverage alone, and ensure before shipping that you didn't write a race by forgetting to take a lock, or taking the wrong lock, in any of the code that your team controls. Then your code will be in the best possible place: Not racy, and not even just accidentally race-free, but race-free by construction.
References
[1] H. Sutter. "The Pillars of Concurrency" (Dr. Dobb's Journal, August 2007; http://www.drdobbs.com/high-performance-computing/200001985).
[2] H. Sutter. "Use Lock Hierarchies to Avoid Deadlock" (Dr. Dobb's Journal, January 2008; http://www.drdobbs.com/high-performance-computing/204801163).


