C++ Boost

Boost.Threads

condition


Introduction
Header
Synopsis
Members
Example

Introduction

An object of class condition is a synchronization primitive used to cause a thread to wait until a particular shared-data condition (or time) is met. A condition object is always used in conjunction with a mutex object modeling a Mutex Concept. The mutex must be locked prior to waiting on the condition, which is ensured by passing a lock object modeling a Lock Concept to the condition object's wait functions. While the thread is waiting on the condition object, the mutex associated with the lock is unlocked. When the thread returns from a call to one of the condition object's wait functions, the mutex is again locked. The tricky lock/unlock/lock sequence is performed automatically by the condition object's wait functions.

The condition type is often used to implement the Monitor Object and other important patterns. See [Schmidt-00] and [Hoare 74]. Monitors are one of the most important patterns for creating reliable multithreaded programs.

See Formal Definitions for definitions of thread states blocked and ready. Note that "waiting" is a synonym for blocked.

Header

#include <boost/thread/condition.hpp>

Synopsis

namespace boost { 

class condition : private boost::noncopyable // Exposition only.
   // Class condition meets the NonCopyable requirement.
{
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 xtime& xt);
   template <typename ScopedLock, typename Predicate>
      bool timed_wait(ScopedLock& lock, const xtime& xt, Predicate pred);
};

} // namespace boost

Members


Constructor

condition();

Effects: Constructs a condition.


Destructor

~condition();

Effects: Destroys *this.


notify_one

void notify_one();

Effects: If there is a thread waiting on *this, change that thread's state to ready. Otherwise there is no effect.

Notes: If more that one thread is waiting on the condition, it is unspecified which is made ready.


notify_all

void notify_all();

Effects: Change the state of all threads waiting on *this to ready. If there are no waiting threads, notify_all() has no effect.


wait

template <typename ScopedLock>
   void wait(ScopedLock& lock);

Requires: ScopedLock meets the ScopedLock requirements.

Effects: Releases the lock on the mutex model associated with lock, blocks the current thread of execution until readied by a call to this->notify_one() or this->notify_all(), and then reacquires the lock. All effects occur in an atomic fashion.

Throws: lock_error if !lock.locked()

Danger: This version should always be used within a loop checking that the state logically associated with the condition has become true. Without the loop, race conditions can ensue due to possible "spurious wake ups". The second version encapsulates this loop idiom internally and is generally the preferred method.

template <typename ScopedLock, typename Pr>
   void wait(ScopedLock& lock, Pr pred);

Requires: ScopedLock meets the ScopedLock requirements, return from pred() convertible to bool.

Effects: As if:

   while (!pred()) wait(lock)

Throws: lock_error if !lock.locked()


timed_wait

template <typename ScopedLock>
   bool timed_wait(ScopedLock& lock, const xtime& xt);

Requires: ScopedLock meets the ScopedLock requirements.

Effects: Releases the lock on the mutex model associated with the lock, blocks the current thread of execution until readied by a call to this->notify_one() or this->notify_all(), or until xt, and then reacquires the lock. All effects occur in an atomic fashion.

Throws: lock_error if !lock.locked()

Danger: This version should always be used within a loop checking that the state logically associated with the condition has become true. Without the loop, race conditions can ensue due to "spurious wake ups". The second version encapsulates this loop idiom internally and is generally the preferred method.

Returns: false if xt is reached, otherwise true.

template <typename ScopedLock, typename Pr>
   bool timed_wait(ScopedLock& lock, const xtime& xt, Pr pred);

Requires: ScopedLock meets the ScopedLock requirements, return from pred() convertible to bool.

Effects: As if:

   while (!pred())
   {
      if (!timed_wait(lock, xt))
         return false;
   }

Throws: lock_error if !lock.locked()

Returns: false if xt is reached, otherwise true.


Example Usage

#include <iostream>
#include <vector>
#include <boost/utility.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/thread.hpp>

class bounded_buffer : private boost::noncopyable
{
public:
    typedef boost::mutex::scoped_lock lock;

    bounded_buffer(int n) : begin(0), end(0), buffered(0), circular_buf(n) { }

    void send (int m) {
        lock lk(monitor);
        while (buffered == circular_buf.size())
            buffer_not_full.wait(lk);
        circular_buf[end] = m;
        end = (end+1) % circular_buf.size();
        ++buffered;
        buffer_not_empty.notify_one();
    }
    int receive() {
        lock lk(monitor);
        while (buffered == 0)
            buffer_not_empty.wait(lk);
        int i = circular_buf[begin];
        begin = (begin+1) % circular_buf.size();
        --buffered;
        buffer_not_full.notify_one();
        return i;
    }

private:
    int begin, end, buffered;
    std::vector<int> circular_buf;
    boost::condition buffer_not_full, buffer_not_empty;
    boost::mutex monitor;
};

bounded_buffer buf(2);

void sender() {
    int n = 0;
    while (n < 100) {
       buf.send(n);
       std::cout << "sent: " << n << std::endl;
       ++n;
    }
    buf.send(-1);
}

void receiver() {
    int n;
    do {
       n = buf.receive();
       std::cout << "received: " << n << std::endl;
    } while (n != -1); // -1 indicates end of buffer
}

int main(int, char*[])
{
    boost::thread thrd1(&sender);
    boost::thread thrd2(&receiver);
    thrd1.join();
    thrd2.join();
    return 0;
}

Typical output (dependent on scheduling policies) is:

sent: 0
sent: 1
received: 0
received: 1
sent: 2
sent: 3
received: 2
received: 3
sent: 4
received: 4

Revised 05 November, 2001

© Copyright William E. Kempf 2001 all rights reserved.