Channels ▼
RSS

Parallel

Associate Mutexes with Data to Prevent Races


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).


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video