
On Aug 25, 2007, at 11:11 AM, Peter Dimov wrote:
Howard Hinnant:
On Aug 22, 2007, at 11:55 AM, Howard Hinnant wrote:
Here's checked_condition:
After investigating the use cases for the "unchecked condition", I realized that my earlier checked_condition that I posted here wasn't quite right, at least if you considered it a debug tool. To this end I've rewritten it, and renamed it to condition_debug to emphasize its role. The previous version was allowing default constructed conditions to wait on different mutexes at the same time. This revised condition_debug allows default constructed conditions to only wait on different mutexes at different times. All simultaneous waits must be on the same mutex (even for a default constructed condition).
...
The checks seem too strict. It's valid to notify_all and then rebind the condition to a different mutex, but your condition_debug will likely assert/throw as the awakened threads have not yet executed their postchecks. (Unless I'm overlooking something.)
It's quite likely that only the implementor of pthread_cond_t may catch all errors in the general case. This does not diminish the utility of a checked condition that catches most errors in the typical case IMO.
You're right. A (fully) debugging condition is at best extremely difficult to implement. That's probably why POSIX made this behavior undefined instead of specifying an error code return: http://www.unix.org/single_unix_specification/
When a thread waits on a condition variable, having specified a particular mutex to either the pthread_cond_timedwait() or the pthread_cond_wait() operation, a dynamic binding is formed between that mutex and condition variable that remains in effect as long as at least one thread is blocked on the condition variable. During this time, the effect of an attempt by any thread to wait on that condition variable using a different mutex is undefined. Once all waiting threads have been unblocked (as by the pthread_cond_broadcast() operation), the next wait operation on that condition variable shall form a new dynamic binding with the mutex specified by that wait operation. Even though the dynamic binding between condition variable and mutex may be removed or replaced between the time a thread is unblocked from a wait on the condition variable and the time that it returns to the caller or begins cancellation cleanup, the unblocked thread shall always re-acquire the mutex specified in the condition wait operation call from which it is returning.
I'm currently thinking that we should drop the condition(Mutex&) constructor from condition: template <class Mutex> class condition { public: typedef Mutex mutex_type; condition(); ~condition(); condition(const condition&) = delete; condition& operator=(const condition&) = delete; void notify_one(); void notify_all(); template <class Lock> void wait(Lock& lock); // If ! lock.owns(), condition_error thrown template <class Lock, class Predicate> void wait(Lock& lock, Predicate pred); // If ! lock.owns(), condition_error thrown template <class Lock> // If ! lock.owns(), condition_error thrown bool timed_wait(Lock& lock, const utc_time& abs_time); template <class Lock, class Predicate> // If ! lock.owns(), condition_error thrown bool timed_wait(Lock& lock, const utc_time& abs_time, Predicate pred); }; Adding the condition(Mutex&) constructor which does nothing but specify undefined behavior provides no benefit, and quite possibly adds cost to valid use cases of condition which use the default constructor instead. * The condition(Mutex&) constructor would not provide any guarantee of safety. * If an implementation did provide the error check, clients who had valid use cases using the default constructor would pay for the error checking even though they did not need or want it. * A fully precise debugging condition appears difficult and/or expensive. (worst of both worlds) The above being said, it is easy to add a "constrained condition" with a variety of syntaxes and semantics. A "constrained condition" would be one where the dynamic binding between the condition and the mutex is set at construction time, and can not be altered throughout the lifetime of the condition. The chief motivation for the constrained condition is that the majority of use cases fit into the "constrained use" pattern. I've shown a use case where the violation of this binding is a correctable run time event, thus making an exception appropriate. It might be appropriate to disallow default construction of a constrained condition. I can imagine several different syntaxes for a constrained condition: condition<constrain<Mutex>> cv(mut); constrain_condition<Mutex> cv(mut); constrain::condition<Mutex> cv(mut); All of the above syntaxes could be achieved either by the std::lib, or by the client. Here is an example implementation of the first syntax. It is fairly easy to write, and as far as I know, has no subtleties. template <class Mutex> struct constrain {}; template <class Mutex> class condition<constrain<Mutex>> { public: typedef Mutex mutex_type; private: condition<mutex_type> cv_; mutex_type& ext_mut_; public: explicit condition(mutex_type& mut) : ext_mut_(mut) {} void notify_one() {cv_.notify_one();} void notify_all() {cv_.notify_all();} template <class Lock> void wait(Lock& lock) { check(lock); cv_.wait(lock); } template <class Lock, class Predicate> void wait(Lock& lock, Predicate pred) { check(lock); cv_.wait(lock, std::move(pred)); } template <class Lock> bool timed_wait(Lock& lock, const utc_time& abs_time) { check(lock); cv_.timed_wait(lock, abs_time); } template <class Lock, class Predicate> bool timed_wait(Lock& lock, const utc_time& abs_time, Predicate pred) { check(lock); cv_.timed_wait(lock, abs_time, std::move(pred)); } private: template <class Lock> void check(const Lock& lock); }; template <class Mutex> template <class Lock> void condition<constrain<Mutex>>::check(const Lock& lock) { if (lock.mutex() != &ext_mut_) throw condition_error(); } The reason to make the constrained condition a different type from the unconstrained condition is because the two have different semantics. And there exist valid use cases for both semantics. -Howard