From: David Abrahams (dave_at_[hidden])
Date: 2002-10-16 20:35:47
Jonathan Biggar <jon_at_[hidden]> writes:
> David Abrahams wrote:
> > > > b. What is the rationale for condition::wait() throwing
> > > > lock_error if !lock.locked()? Would it not be cleaner to
> > > > simply lock the lock if it arrives in an unlocked
> > > > condition?
> > >
> > > It is almost certainly an error to call condition::wait()
> > > without having the lock, since the application code is
> > > responsible for testing the application data that the condition
> > > and lock protect. If you aren't holding the lock, you either
> > > have a race condition or a deadlock lurking.
> > I also realize that, given the way condition::wait() is
> > implemented. Did you read my 2nd sentence? If I don't have the
> > lock, and I want to wait on the condition, why shouldn't it just
> > acquire the lock for me? Use case: the condition protects some
> > input queue. I want to block until some data is available in the
> > queue. I don't need to touch the queue until there's data ready,
> > so I have no reason to lock the mutex ahead of time other than
> > that's what the condition interface asks me to do.
> That use case will fail as described. You must test the queue to
> see if it already has data in it before calling wait or you might
> never get woken up, and that test must be done with the lock held to
> avoid a race.
I'm talking about the version of wait which takes a predicate. I
presume the predicate is what you'd use to test the queue anyway?
> I've never seen a workable use case for calling wait() on a
> condition without performing some test on the data that the
> condition is protectiong, and that means the lock has to be acquired
> first. That's the basic design pattern for using locks & condition
> while (!predicate())
> // modify protected data here
Right. But I'm suggesting that everything up to the comment can be
encapsultated in the version of wait that takes the predicate.
> Of course to be exception safe, you use a lock guard rather than
> just acquiring and releasing the lock.
> > It's always more pleasant to use an interface which "always works"
> > than one which throws at you to let you know it doesn't like the
> > state of your inputs.
> It would be nice, but that's not the way to make this "always work".
Maybe I'm still missing something, but I don't see it yet. The version
of wait that takes a predicate already contains code for testing the
predicate and in a loop. I don't see a reason to make the user test it
and branch around the wait. But then, maybe I'm describing a slightly
higher-level construct that just shouldn't be called a condition wait.
> > > This is only a race condition if the code violates the
> > > pre-condition that the lock must be locked before testing the
> > > predicate and calling wait().
> > It's not a precondition. The behavior in case the lock is not lock
> > is well-defined by the documentation, and lock.locked() is not in
> > the Requires: clause. It doesn't make sense to specify behavior in
> > the case of precondition violations.
> It is a precondition in an informal sense, in that wait() throws
> rather than doing what it is expected to do.
That informal sense just confuses things. We're talking about formal
documentation. There's a race condition in the way the wait() with a
predicate is described -- the same race condition you're concerned
about above, in fact. It should be fixed.
> > > > f. The non-predicate form of timed_wait Effects: clause ends
> > > > with "...and then reacquires the lock". This appears to mean
> > > > the lock is reacquired regardless of whether timeout arrives
> > > > before notification. I think that can't be intentional. Can
> > > > it?
> > >
> > > Yes, that is intentional. The required condition variable pre- and
> > > post-condition logic for wait() is that the associated lock is locked.
> > > Without that, you get race conditions.
> > Only if you access the associated data, right? Well, it wasn't
> > completely clear to me that that was supposed to be allowed in the
> > case of timeout. It means that even with a timeout, you can't come
> > back from wait() if some other thread has the mutex locked. I
> > think it would be good to spell that out somewhere in the
> > condition docs.
> Right, but you have to access the associated data to see if you are
> going to call cond.timed_wait() in the first place.
No, I mean /after/ the return from timed_wait. At that point, if you
aren't going to touch the data, you don't need to have the mutex. If
you're going to touch it regardless of whether notify() occurred. You
-- David Abrahams dave_at_[hidden] * http://www.boost-consulting.com Building C/C++ Extensions for Python: Dec 9-11, Austin, TX http://www.enthought.com/training/building_extensions.html
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk