A condition variable is a way for two threads to signal each other based on some predicate, such as a queue being empty or full. This is represented by Metrowerks::condition.
class condition { public: condition(); ~condition(); void notify_one(); void notify_all(); template <typename ScopedLock> void wait(ScopedLock& lock); template <typename ScopedLock, typename Predicate> void wait(ScopedLock& lock, Predicate pred); template <typename ScopedLock> bool timed_wait(ScopedLock& lock, const universal_time& unv_time); template <typename ScopedLock, typename Predicate> bool timed_wait(ScopedLock& lock, const universal_time& unv_time, Predicate pred); template <typename ScopedLock, typename Predicate> bool timed_wait(ScopedLock& lock, const elapsed_time& elps_time, Predicate pred); };
Note that condition is not copyable nor assignable.
A condition allows one thread to pass a locked lock to the condition's wait function. The current thread then atomically unlocks the locks and goes to sleep. It will stay asleep until another thread calls this condition's notify_one() or notify_all() member function. The original thread will then atomically awake and lock the lock.
The difference between notify_one and notify_all is that the former notifies only one thread waiting on the condition, whereas the latter notifies all threads waiting on the condition.
When using the variation of the wait function without the predicate, it is important that you recheck the predicate (data) you were waiting for when the wait returns. You can not assume that whatever it is that you were wanting to be true is now true. This is most easily done by calling the wait within a while loop:
Metrowerks::condition cond;
...
Metrowerks::mutex::scoped_lock lock(some_mutex);
while (I_need_more_data)
cond.wait(lock);
It is up to some other thread to make I_need_more_data false, and it will likely need to lock some_mutex in order to do it. When it does, it should execute one of:
cond.notify_one();
or
cond.notify_all();
It must also unlock some_mutex to allow the other thread's wait to return. But it does not matter whether some_mutex gets unlocked before or after the notification call. Once the original wakes from the wait, then the signal is satisfied. Should it wait again, then another thread will have to renotify it.
If it is more convenient, you can pass a predicate to the wait function, which will then do the while loop for you. Note that there are also several timed waits if you want to limit the sleep time (which can be thought of as an additional "condition" on the system clock).
Example of condition usage is a full example of condition usage. One thread puts stuff into a queue while another thread reads stuff back out of the other end.
#include <iostream> #include <queue> #include <ewl_thread> class unbounded_queue { public: typedef Metrowerks::mutex Mutex; typedef Mutex::scoped_lock Lock; void send (int m); int receive(); private: std::queue<int> the_queue_; Metrowerks::condition queue_is_empty_so_; Mutex mut_; }; void unbounded_queue::send (int m) { Lock lock(mut_); the_queue_.push(m); std::cout << "sent: " << m << ' if (the_queue_.size() == 1) queue_is_empty_so_.notify_one(); } int unbounded_queue::receive() { Lock lock(mut_); while (the_queue_.empty()) queue_is_empty_so_.wait(lock); int i = the_queue_.front(); std::cout << "received: " << i << ' the_queue_.pop(); return i; } unbounded_queue buf; void sender() { int n = 0; while (n < 1000) { buf.send(n); ++n; } buf.send(-1); } void receiver() { int n; do { n = buf.receive(); } while (n >= 0); } int main() { Metrowerks::thread send(sender); Metrowerks::thread receive(receiver); send.join(); receive.join(); }
In the above example one thread continually sends data to a std::queue, while another thread reads data out of the queue. The reader thread must wait if the queue is empty, and the sender thread must notify the reader thread (to wake up) if the queue changes from empty to non-empty.
An interesting exercise is to transform the above example into a "bounded queue". That is, there is nothing from stopping the above example's queue from sending all of the data before the receiver thread wakes up and starts consuming it.
Example of queue limitation is an example if you wanted to limit the above queue to a certain number of elements (like 20).
#include <iostream> #include <cdeque> #include <ewl_thread> class bounded_queue { public: typedef Metrowerks::mutex Mutex; typedef Mutex::scoped_lock Lock; typedef Metrowerks::cdeque<int> Queue; bounded_queue(int max) {the_queue_.reserve((unsigned)max);} void send (int m); int receive(); private: Queue the_queue_; Metrowerks::condition queue_is_empty_so_; Metrowerks::condition queue_is_full_so_; Mutex mut_; }; template <class C> struct container_not_full { container_not_full(const C& c) : c_(c) {} bool operator()() const {return c_.size() != c_.capacity();} private: const C& c_; }; template <class C> struct container_not_empty { container_not_empty(const C& c) : c_(c) {} bool operator()() const {return !c_.empty();} private: const C& c_; }; void bounded_queue::send (int m) { Lock lock(mut_); queue_is_full_so_.wait(lock, container_not_full<Queue>(the_queue_)); the_queue_.push_back(m); std::cout << "sent: " << m << ' if (the_queue_.size() == 1) queue_is_empty_so_.notify_one(); } int bounded_queue::receive() { Lock lock(mut_); queue_is_empty_so_.wait(lock, container_not_empty<Queue>(the_queue_)); int i = the_queue_.front(); std::cout << "received: " << i << ' if (the_queue_.size() == the_queue_.capacity()) queue_is_full_so_.notify_one(); the_queue_.pop_front(); return i; } bounded_queue buf(20); void sender() { int n = 0; while (n < 1000) { buf.send(n); ++n; } buf.send(-1); } void receiver() { int n; do { n = buf.receive(); } while (n >= 0); } int main() { Metrowerks::thread send(sender); Metrowerks::thread receive(receiver); send.join(); receive.join(); }
The above example actually demonstrates more than was advertised. Not only does it limit the queue length to 20, it also introduces a non-std container (Metrowerks::cdeque) which easily enables the monitoring of maximum queue length. It also demonstrates how more than one condition can be associated with a mutex. And furthermore, it uses the predicate versions of the wait statements so that explicit while loops are not necessary for the waits. Note that the predicates are negated: the wait will loop until the predicate is true.
Condition variables are fairly dangerous in single threaded code. They will compile and do nothing. But note that you may loop forever waiting for a predicate that won't change:
while (the_queue.empty())
queue_not_empty.wait(lk);
If the_queue.empty() is true then this is just an infinite loop in single thread mode. There is no other thread that is going to make the predicate false.