Boost logo

Boost :

From: Roland Schwarz (roland.schwarz_at_[hidden])
Date: 2005-09-18 07:19:28


Yuval Ronen wrote:

>My intuition is to make it work. Meaning that the wait() will unlock
>lock_count() times, to a full unlock state, and then lock it back again
>the same amout of times. Why shouldn't it work?
>
>
Ok, I'll try to explain, altough I am afraid I am not too good in this.
You are true, in believing that it can be made working. But you are
about to create extremely dangerous code by this.

Once you place this semantics into work _every_ subroutine call
from within a locked scope attains wait semantics, i.e the state of
your locked memory might have changed. This wouldn't be expected
by most programmers.

E.g.

...
    {
        scoped_lock l(m);
         // do something to the locked data
         foo(); // call a member function which _might_ wait
         // do something else to the locked data
         // ouch, the locking was removed in between, data has
         // unexpectedly changed. I did not expect this, did you?
    }

Ok, this is no good, but why then allow recursive mutex anyways?

I find it harder to come up with a convincing example, but basically
I think to forbid it entirely simply is too strict. It is possible to write
correct, yet safe programs with recursive_mutex. I'll try:

class deep_thought {
    recursive_mutex m;
    condition c;
    int answer;
    public:
    void think() {
        scoped_lock l(m);
        the_answer_is(42);
        cout << show_me_the_answer();
    }
    void the_answer_is(int n) {
        scoped_lock l(m);
        answer = n;
    }
    int show_me_the_answer() {
        scoped_lock l(m);
        while (answer < 42)
            c.wait(l);
        return answer;
    }
};

You have a single memory location to protect: answer.
So you have to use a single mutex.
You have three public functions that can be called from
other threads, and you found it convenient to also use
them from the same thread by recursively call into them.
So your only choice is to use a recursive mutex.
Everything is ok as long as you are not trying to call the
"show_me_the_answer" with the mutex already locked.

But wait: Even this is correct, if the answer already is
larger than 42. Now correctnes has become a runtime
property. And this is why I think the a reasonable reaction
is to throw an exception. You caused a runtime error.

Of course, better programming style would have choosen
a different grouping of the functions, so the recursive_mutex
could have been avoided entirely. But you also have
things like reinterpret_cast<> which usually are beeing
avoided whenever possible. Still it is there because there
are times when it is useful, you simply have to put a lot
more thought into it when using it.

>And even if I agree, throwing seems weird. As I mentioned, if a
>combination of a condition variable and a recursive mutex is a bad
>thing, then make it not compile at all. Isn't this better? Allowing
>usage of a recursive mutex as long as it's not being used recursively is
>the opposite of logical, to me.
>
>
>
I hope my example was helpful to show you that making it not compile
is way too restrictive.

Roland


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