Boost logo

Boost :

From: Howard Hinnant (howard.hinnant_at_[hidden])
Date: 2007-08-24 10:25:36


On Aug 23, 2007, at 10:35 AM, Peter Dimov wrote:

> On reflection though, I'll change the constructor from
>
> explicit condition( Mutex * pm = 0 );
>
> to
>
> explicit condition( Mutex * pm );
>
> as it's too easy to accidentally disable checking by not including the
> condition in the member init list.

Doesn't this remove goal 2?

2. The freedom to dynamically associate mutexes with condition
variables.

Here's the use case for it:

You have dynamically allocated data protected by a mutex. Say that
data represents something like a car bumper in a car assembly line.
Perhaps we're programming an assembly robot. There is a thread that
gets the data package (which includes the mutex) from a source. The
threads job is to do something with the data, and then wait until
another thread signals that it is ready to receive the processed data:

condition<mutex> cv;
...
while (true)
{
     Data d = get_data();
     unique_lock<mutex> lk = d.get_lock();
     d.process();
     while (d.not_ready_to_pass_on())
         cv.wait(lk);
     stream << d;
}

In the above model, it is irrelevant that we're using the condition to
wait on different mutexes in each iteration of the while loop. We
work on the bumper, then wait until it is time to pass it to the next
robot in the assembly line. Then we get a new bumper (or whatever).

In the above use case, there is only one thread waiting on the
condition. This makes it completely safe to keep rebinding a
different mutex to the condition. With this use case, I believe we
have to support goal #2 (which I credit Peter with bringing to my
attention).

That being said, a very similar use case to that above *is* an error.
Now let's say that we have multiple threads such as that above, all
using the same condition. As soon as two threads wait on the same
condition at the same time, with different mutexes, we step into what
POSIX calls undefined behavior. Though this is not necessarily a non-
recoverable logic error. For example:

We continue with the above use case, but make it a little more
complicated. There are several threads getting the data, processing
it somehow, and waiting on the same cv. But in this model, get_data()
nearly always returns the same data, or at least the same associated
mutex, guarding different values in the data. For example the data
always represents a bumper of a specific make of car, but perhaps
sometimes the bumper is red, sometimes white, sometimes with fog
lights, sometimes without.

struct controller
{

     shared_ptr<Data> d_;
     shared_ptr<condition<mutex>> cv_;

    void initialize() {...}

     void process()
     {
         while (true)
         {
             d_ = get_data();
             unique_lock<mutex> lk = d->get_lock();
             while (d_->not_ready_to_process())
                cv_->wait(lk);
             d_->process();
             stream << *d_;
         }
     }

     void run()
     {
         while (true)
         {
             try
             {
                 process();
             }
             catch (condition_error&)
             {
                  cv_ = get_condition(d_->get_lock());
             }
         }
     }

};

We now hold the condition in a shared_ptr as member data. It will
almost never change value since get_data() nearly always returns the
same mutex. But if get_data() does return a new mutex, then the
condition::wait throws, that exception is caught, and the controller
is essentially re-initialized with a new make of car. This software
can even tolerate a "dynamic retooling" of the assembly line. That
is, some threads may still be working on the last car model while
other threads have finished their work and are already getting started
on the new make of car. As the last thread working on the old model
changes over, it destructs the old condition and acquires the new one.

The above appears to be a reasonable and valid use case to me for
"correcting" the condition/mutex binding. The retooling operation is
a rare (exceptional) event. Using exceptions for this makes the main
processing routine simpler/cleaner as it does not have to deal with
the complication of retooling.

In summary, we have valid use cases for both not checking condition/
mutex consistency, and for checking condition/mutex consistency, with
both use cases built on "correctness" concerns as opposed to
"performance" concerns. Where this leads us next, I'm not yet sure...

-Howard


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk