Semaphores


Semaphores ensure synchronization by protecting thread-exposed resources. This section discusses five types of semaphores providing different types of resource protection.

Recursive, nonrecursive, and simple mutex semaphores are discussed in this section. A recursive mutex counts the number of locks a resource acquires. A recursive mutex releases a resource only when the number of unlocks received equals the number of locks already applied. A simple mutex maintains only a simple locked/unlocked state. No matter how many lock messages are received, a single unlock message releases the resource.  A nonrecursive mutex can only be locked once by the same thread.

Recursive versions of the mutexes (mutex, FIFO, priority, and counting) are grouped together in this section. Discussions of simple versions of the same mutexes follow the recursive mutexes, and the nonrecursive will follow after that.

critical_section

The simplest synchronization object is a critical section. A critical section provides the most efficient protection for thread-exposed resources and should be used in time-critical operations. Critical sections are reentrant; if a thread tries to lock an os_critical_section object the thread already owns, the request succeeds immediately. A corresponding number of unlock operations must occur before the resource is available for another thread.

Critical sections only support the blocking of lock and unlock operations. For situations that require non-blocking resource obtainment, use an os_mutex .

Example <ospace/thread/examples/critical2.cpp>
#include <iostream>
#include <ospace/thread.h>

os_critical_section mutex;
int count = 0;

void*
update( void* args )
  {
  cout << "Thread " << (int) args << " waiting for lock" << endl;
  mutex.lock();
  ++count;
  cout << "Thread " << (int) args << " set value to " << count << endl;
  os_this_thread::sleep( 3 );
  cout << "Thread " << (int) args << " releasing lock" << endl;
  mutex.unlock();
  return 0;
  }

void
main()
  {
  os_thread t1( update, (void*) 1 );
  os_thread t2( update, (void*) 2 );
  os_thread t3( update, (void*) 3 );

  os_this_thread::wait_for_thread( t1 );
  os_this_thread::wait_for_thread( t2 );
  os_this_thread::wait_for_thread( t3 );
  }

Thread 1 waiting for lock
Thread 1 set value to 1
Thread 2 waiting for lock
Thread 3 waiting for lock
Thread 1 releasing lock
Thread 2 set value to 2
Thread 2 releasing lock
Thread 3 set value to 3
Thread 3 releasing lock

mutex

A mutex semaphore locks a shared resource one thread at a time. Use a mutex semaphore to ensure a critical section is executed only one thread at a time. If a thread tries to lock a mutex semaphore it already owns, the request succeeds immediately. A mutex semaphore must be recursive; it must be unlocked as many times as it was locked before another thread can lock it.

In the following code sample, a single instance of a bank account class called account is created. One thread executes deposit( 100 ) . Two threads then execute withdraw ( 80 ) . It is possible for both threads to read the balance and then both decrement the balance by 80.


class account
  {
  public:
    account() :
      balance_( 0 )
      {
      }

    void deposit( int n )
      {
      balance_ += n;
      }

    int withdraw( int n )
      {
      int result = 0;
      if ( balance_ >= n )
        {
        balance_ -= n;
        result = n;
        }
      else
        {
        cout << "Failure: balance left at " << balance_ << endl;
        }
      return result;
      }

  private:
    int balance_;
  };
  

To prevent the problem in the above code sample, add a mutex semaphore object around the critical section. The following example is a thread-aware version of the account class.

Example <ospace/thread/examples/mutex1.cpp>

#include <iostream>
#include <ospace/thread.h>

class account
  {
  public:
    account() :
      balance_( 0 )
      {
      }

    void deposit( int n )
      {
      mutex_.lock();
     	cout << "Account locked by thread " << os_this_thread::tid();
     	cout << endl;
     	cout << "Increment balance of " << balance_ << " by " << n << endl;
      balance_ += n;
      cout << "Balance is now " << balance_ << endl;
      cout << "Unlock account" << endl;
      mutex_.unlock();
      }

    int withdraw( int n )
      {
      mutex_.lock();
      cout << "Account locked by thread " << os_this_thread::tid();
      cout << endl;
      cout << "Attempt to withdraw " << n;
      cout << " from balance of " << balance_ << endl;
      int result = 0;
      if ( balance_ >= n )
        {
        balance_ -= n;
        cout << "Success: balance is now " << balance_ << endl;
        result = n;
        }
      else
        {
        cout << "Failure: balance left at " << balance_ << endl;
        }
      cout << "Unlock account" << endl;
      mutex_.unlock();
      return result;
      }

  private:
    os_mutex_semaphore mutex_;
    int balance_;
  };

account shared_account; // Global shared account.

void*
user( void* args )
  {
  shared_account.deposit( 50 );
  os_this_thread::sleep( 1 );
  shared_account.withdraw( 80 );
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  os_thread t1( user ); // Spawn new thread.
  os_thread t2( user ); // Spawn new thread.

  os_this_thread::wait_for_thread( t1 );
  os_this_thread::wait_for_thread( t2 );
  }

Account locked by thread 4
Increment balance of 0 by 50
Balance is now 50
Unlock account
Account locked by thread 6
Increment balance of 50 by 50
Balance is now 100
Unlock account
Account locked by thread 6
Attempt to withdraw 80 from balance of 100
Success: balance is now 20
Unlock account
Account locked by thread 4
Attempt to withdraw 80 from balance of 20
Failure: balance left at 20
Unlock account

fifo_mutex

The os_fifo_mutex class provides the same functionality as the standard os_mutex , except that thread acquisition order is scheduled on a first-in, first-out (FIFO) basis. This can be used to prevent resource "starvation" for a thread, because every thread will obtain the mutex in a predictable order. Because os_fifo_mutex has slightly more overhead than the standard os_mutex , a FIFO mutex should be used only in situations where guaranteed acquisition order is important.

If a thread tries to lock a FIFO mutex semaphore it already owns, the request succeeds immediately. A FIFO mutex semaphore must be recursive; it must be unlocked as many times as it was locked before another thread can lock it.

This example illustrates the benefit of FIFO mutexes by synchronizing four threads on a single os_fifo_mutex . Note that the order of mutex obtainment appears in the same order as the acquisition attempts.

Example <ospace/thread/examples/rfifo.cpp>
#include <iostream>
#include <ospace/thread.h>

os_fifo_mutex mutex;

void*
thread( void* args )
  {
  os_thread_t tid = os_this_thread::tid();

  cout << "Thread " << tid << " waiting for mutex." << endl;
  mutex.lock();
  cout << "Thread " << tid << " obtained mutex." << endl;
  os_this_thread::sleep( 3 );

  cout << "Thread " << tid << " obtaining second lock." << endl;
  mutex.lock();
  os_this_thread::sleep( 3 );
  cout << "Thread " << tid << " releasing lock on mutex." << endl;
  mutex.unlock();

  cout << "Thread " << tid << " releasing lock on mutex." << endl;
  mutex.unlock();
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  os_thread a( thread ); // Spawn new thread.
  os_thread b( thread ); // Spawn new thread.
  os_thread c( thread ); // Spawn new thread.
  os_thread d( thread ); // Spawn new thread.
  os_this_thread::wait_for_thread( d );
  }

Thread 4 waiting for mutex.
Thread 4 obtained mutex.
Thread 6 waiting for mutex.
Thread 7 waiting for mutex.
Thread 8 waiting for mutex.
Thread 4 obtaining second lock.
Thread 4 releasing lock on mutex.
Thread 4 releasing lock on mutex.
Thread 6 obtained mutex.
Thread 6 obtaining second lock.
Thread 6 releasing lock on mutex.
Thread 6 releasing lock on mutex.
Thread 7 obtained mutex.
Thread 7 obtaining second lock.
Thread 7 releasing lock on mutex.
Thread 7 releasing lock on mutex.
Thread 8 obtained mutex.
Thread 8 obtaining second lock.
Thread 8 releasing lock on mutex.
Thread 8 releasing lock on mutex.

priority_mutex

The os_priority_mutex class provides the same functionality as the standard os_mutex , except that thread acquisition order is scheduled on a fixed-priority basis. The next thread with the highest priority is guaranteed to acquire the mutex. Threads of equal priorities are scheduled on a first-in, first-out (FIFO) basis. Because priority mutex has slightly more overhead than the standard os_mutex , a priority mutex should only be used in situations where guaranteed acquisition order is important.

If a thread tries to lock a priority mutex semaphore it already owns, the request succeeds immediately. A priority mutex semaphore must be recursive; it must be unlocked as many times as it was locked before another thread can lock it.

This example illustrates the benefit of priority mutexes by synchronizing four threads on a single os_priority_mutex . Note that the order of mutex acquisition can be predicted due to the various priority levels.

Example <ospace/thread/examples/rpriority.cpp>
#include <iostream>
#include <ospace/thread.h>

os_priority_mutex mutex;

void*
thread( void* args )
  {
  os_thread_t tid = os_this_thread::tid();
  int priority = (int) args;
  cout << "Thread " << tid << " waiting for mutex." << endl;
  mutex.lock( priority );
  cout << "Thread " << tid << " obtained mutex." << endl;
  os_this_thread::sleep( 3 );
  cout << "Thread " << tid << " obtaining second lock." << endl;
  mutex.lock( priority );
  os_this_thread::sleep( 3 );
  cout << "Thread " << tid << " releasing lock on mutex." << endl;
  mutex.unlock();
  os_this_thread::sleep( 3 );
  cout << "Thread " << tid << " releasing lock on mutex." << endl;
  mutex.unlock();
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  mutex.lock();
  // Order of finish:
  //   a
  //   b
  //   c
  //   d
  os_thread a( thread, (void*) 0 ); // Spawn new thread.
  os_thread b( thread, (void*) 10 ); // Spawn new thread.
  os_thread c( thread, (void*) 5 ); // Spawn new thread.
  os_thread d( thread, (void*) 20 ); // Spawn new thread.

  mutex.unlock();
  os_this_thread::wait_for_thread( a );
  }

Thread 4 waiting for mutex.
Thread 5 waiting for mutex.
Thread 6 waiting for mutex.
Thread 7 waiting for mutex.
Thread 7 obtained mutex.
Thread 7 obtaining second lock.
Thread 7 releasing lock on mutex.
Thread 7 releasing lock on mutex.
Thread 5 obtained mutex.
Thread 5 obtaining second lock.
Thread 5 releasing lock on mutex.
Thread 5 releasing lock on mutex.
Thread 6 obtained mutex.
Thread 6 obtaining second lock.
Thread 6 releasing lock on mutex.
Thread 6 releasing lock on mutex.
Thread 4 obtained mutex.
Thread 4 obtaining second lock.
Thread 4 releasing lock on mutex.
Thread 4 releasing lock on mutex.

simple_mutex

An os_simple_mutex is similar to a regular mutex semaphore, except a simple mutex is not recursive. A simple mutex maintains only a simple locked/unlocked state. If a thread tries to lock a simple mutex it already owns, the request succeeds immediately. Unlike an os_mutex , a single unlock operation completely frees the resource so that another thread can obtain ownership. It is illegal for a thread to unlock a simple mutex more than once without first reobtaining ownership.

The following example illustrates how a simple mutex can be locked multiple times by a single thread, but is released by a single unlock operation.

Example <ospace/thread/examples/simple.cpp>
#include <iostream>
#include <ospace/thread.h>

os_simple_mutex mutex;

void*
thread( void* args )
  {
  os_thread_t tid = os_this_thread::tid();
  cout << "Thread " << tid << " waiting for mutex." << endl;
  mutex.lock();
  cout << "Thread " << tid << " obtained mutex." << endl;
  os_this_thread::sleep( 3 );
  cout << "Thread " << tid << " obtaining second/third lock." << endl;
  mutex.lock();
  mutex.lock();
  os_this_thread::sleep( 3 );
  cout << "Thread " << tid << " releasing lock on mutex." << endl;
  mutex.unlock();
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  os_thread a( thread ); // Spawn new thread.
  os_thread b( thread ); // Spawn new thread.
  os_thread c( thread ); // Spawn new thread.
  os_thread d( thread ); // Spawn new thread.

  os_this_thread::wait_for_thread( a );
  os_this_thread::wait_for_thread( b );
  os_this_thread::wait_for_thread( c );
  os_this_thread::wait_for_thread( d );
  }

Thread 4 waiting for mutex.
Thread 4 obtained mutex.
Thread 6 waiting for mutex.
Thread 7 waiting for mutex.
Thread 8 waiting for mutex.
Thread 4 obtaining second/third lock.
Thread 4 releasing lock on mutex.
Thread 6 obtained mutex.
Thread 6 obtaining second/third lock.
Thread 6 releasing lock on mutex.
Thread 8 obtained mutex.
Thread 8 obtaining second/third lock.
Thread 8 releasing lock on mutex.
Thread 7 obtained mutex.
Thread 7 obtaining second/third lock.
Thread 7 releasing lock on mutex.

simple_fifo_mutex

An os_simple_fifo_mutex is similar to a regular FIFO mutex, except that it is not recursive. A simple FIFO mutex maintains a simple locked/unlocked state. If a thread tries to lock a simple FIFO mutex it already owns, the request succeeds immediately. Unlike an os_fifo_mutex , a single unlock operation completely frees the resource so that another thread can obtain ownership. It is illegal for a thread to unlock a simple FIFO mutex more than once without first reobtaining ownership.

This example illustrates how a simple FIFO mutex can be locked multiple times by a single thread, but is released by a single unlock operation.

Example <ospace/thread/examples/sfifo.cpp>
#include <iostream>
#include <ospace/thread.h>

os_simple_fifo_mutex mutex;

void*
thread( void* args )
  {
  os_thread_t tid = os_this_thread::tid();

  cout << "Thread " << tid << " waiting for mutex." << endl;
  mutex.lock();
  cout << "Thread " << tid << " obtained mutex." << endl;
  os_this_thread::sleep( 3 );

  cout << "Thread " << tid << " obtaining second/third lock." << endl;
  mutex.lock();
  mutex.lock();
  os_this_thread::sleep( 3 );

  cout << "Thread " << tid << " releasing lock on mutex." << endl;
  mutex.unlock();
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  os_thread a( thread ); // Spawn new thread.
  os_thread b( thread ); // Spawn new thread.
  os_thread c( thread ); // Spawn new thread.
  os_thread d( thread ); // Spawn new thread.
  os_this_thread::wait_for_thread( d );
  }

Thread 4 waiting for mutex.
Thread 4 obtained mutex.
Thread 6 waiting for mutex.
Thread 7 waiting for mutex.
Thread 8 waiting for mutex.
Thread 4 obtaining second/third lock.
Thread 4 releasing lock on mutex.
Thread 6 obtained mutex.
Thread 6 obtaining second/third lock.
Thread 6 releasing lock on mutex.
Thread 7 obtained mutex.
Thread 7 obtaining second/third lock.
Thread 7 releasing lock on mutex.
Thread 8 obtained mutex.
Thread 8 obtaining second/third lock.
Thread 8 releasing lock on mutex.

simple_priority_mutex

An os_simple_priority_mutex is similar to a regular priority mutex, except a simple priority mutex is not recursive. A simple priority mutex maintains a simple locked/unlocked state. If a thread tries to lock a simple priority mutex it already owns, the request succeeds immediately. Unlike an os_priority_mutex , a single unlock operation completely frees the resource so that another thread can obtain ownership. It is illegal for a thread to unlock a simple priority mutex more than once without first reobtaining ownership.

This example illustrates how a simple priority mutex can be locked multiple times by a single thread, but is released by a single unlock operation.

Example <ospace/thread/examples/spriority.cpp>
#include <iostream>
#include <ospace/thread.h>

os_simple_priority_mutex mutex;

void*
thread( void* args )
  {
  os_thread_t tid = os_this_thread::tid();
  int priority = (int) args;

  cout << "Thread " << tid << " waiting for mutex." << endl;
  mutex.lock( priority );
  cout << "Thread " << tid << " obtained mutex." << endl;
  os_this_thread::sleep( 3 );

  cout << "Thread " << tid << " obtaining second/third lock." << endl;
  mutex.lock();
  mutex.lock();
  os_this_thread::sleep( 3 );

  cout << "Thread " << tid << " releasing lock on mutex." << endl;
  mutex.unlock();
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  mutex.lock();

  // Order of finish:
  //   a
  //   b
  //   c
  //   d
  os_thread a( thread, (void*) 0 ); // Spawn new thread.
  os_thread b( thread, (void*) 10 ); // Spawn new thread.
  os_thread c( thread, (void*) 5 ); // Spawn new thread.
  os_thread d( thread, (void*) 20 ); // Spawn new thread.

  mutex.unlock();
  os_this_thread::wait_for_thread( a );
  }

Thread 4 waiting for mutex.
Thread 5 waiting for mutex.
Thread 6 waiting for mutex.
Thread 7 waiting for mutex.
Thread 7 obtained mutex.
Thread 7 obtaining second/third lock.
Thread 7 releasing lock on mutex.
Thread 5 obtained mutex.
Thread 5 obtaining second/third lock.
Thread 5 releasing lock on mutex.
Thread 6 obtained mutex.
Thread 6 obtaining second/third lock.
Thread 6 releasing lock on mutex.
Thread 4 obtained mutex.
Thread 4 obtaining second/third lock.
Thread 4 releasing lock on mutex.

counting_semaphore

A counting semaphore is a generalization of a mutex semaphore that locks a shared resource against simultaneous access by multiple threads. All resource allocation functions, such as wait() and post() , take an optional parameter defining the number of resources to allocate or deallocate. There are two ways to construct a counting semaphore.

Constructor
os_counting_semaphore( initial )
Initializes the semaphore to start with initial resources. There is no upper bound for the number of resources added to the semaphore.
Constructor
os_counting_semaphore( initial , maximum )
Initializes the semaphore to start with initial resources and limits the maximum number of resources added to the semaphore. If an attempt is made to add more than the specified maximum , an error occurs.

To prevent unnecessary deadlock situations, resource allocation is atomic. For example, if a counting semaphore is constructed with four initial resources, and two threads request three resources each, one thread is guaranteed to succeed. It is impossible for each thread to obtain just two resources and block waiting for the last resource.

The following example uses a counting semaphore to block a reader thread until three numbers are ready to process.

Example <ospace/thread/examples/count1.cpp>
#include <iostream>
#include <stdlib.h> // For rand().
#include <list>
#include <ospace/thread.h>

list< int > list1; // Global shared list.
os_monitor< list< int > > numbers( list1 ); // Monitored list.
os_counting_semaphore counter( 0 );

void*
writer( void* args )
  {
  numbers.write_lock();
  cout << "thread " << os_this_thread::tid() << " enters write" << endl;
  os_this_thread::sleep( 2 );
  int number = rand() % 10;
  cout << "write " << number << endl;
  numbers->push_back( number );
  counter.post();
  cout << "thread " << os_this_thread::tid() << " exits write" << endl;
  numbers.write_unlock();
  return 0;
  }

void*
reader( void* args )
  {
  cout << "reader waits for input" << endl;
  counter.wait( 3 );
  numbers.read_lock();
  cout << "thread " << os_this_thread::tid() << " enters read" << endl;
  for ( int i = 1; i <= 3; i++ )
    {
    int number = numbers->back();
    cout << "read " << number << endl;
    numbers->pop_back();
    }
  cout << "thread " << os_this_thread::tid() << " exits read" << endl;
  numbers.read_unlock();
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  os_thread a( writer ); // Spawn new thread.
  os_thread b( writer ); // Spawn new thread.
  os_thread c( writer ); // Spawn new thread.
  os_thread d( reader ); // Spawn new thread.

  os_this_thread::wait_for_thread( a );
  os_this_thread::wait_for_thread( b );
  os_this_thread::wait_for_thread( c );
  os_this_thread::wait_for_thread( d );
  }

thread 4 enters write
reader waits for input
write 8
thread 4 exits write
thread 6 enters write
write 8
thread 6 exits write
thread 7 enters write
write 3
thread 7 exits write
thread 8 enters read
read 3
read 8
read 8
thread 8 exits read

read_write_semaphore

In some cases, use of a mutex semaphore to lock a shared resource against simultaneous access by multiple threads is too restrictive. For example, there is no reason multiple threads should be prevented from simultaneously reading an account balance, as in the mutex code example Example .

When simultaneous read access by multiple threads is desired, use a read/write semaphore. This semaphore limits access of an object to a single writer, but to multiple readers. A read/write semaphore grants a read lock to multiple readers or a write lock to a single writer.

If a thread tries to lock a read/write semaphore the thread already owns, the request succeeds immediately. A read/write semaphore must be recursive; it must be unlocked as many times as it was locked before another thread can lock it.

The following code is a rewrite of the account class introduced in the example Example . The code below takes advantage of a read/write semaphore.

Example <ospace/thread/examples/rwsem1.cpp>
#include <iostream>
#include <ospace/thread.h>

class account
  {
  public:
    account() :
      balance_( 0 )
      {
      }

    int balance() const
      {
      lock_.read_lock();  // Note: read_lock allowed on const semaphore
      cout << "read locked by thread " << os_this_thread::tid() << endl;
      int tmp = balance_;
      os_this_thread::sleep( 2 );
      cout << "read unlock account" << endl;
      lock_.read_unlock();
      return tmp;
      }

    void deposit( int n )
      {
      lock_.write_lock();
      cout << "write locked by thread " << os_this_thread::tid()
           << endl;
      cout << "increment balance of " << balance_ << " by " << n << endl;
      balance_ += n;
      cout << "Balance is now " << balance_ << endl;
      os_this_thread::sleep( 2 );
      cout << "write unlock account" << endl;
      lock_.write_unlock();
      }

    int withdraw( int n )
      {
      lock_.write_lock();
      cout << "write locked by thread " << os_this_thread::tid()
           << endl;
      cout << "attempt to withdraw " << n;
      cout << " from balance of " << balance_ << endl;
      int result = 0;
      if ( balance_ >= n )
        {
        balance_ -= n;
        cout << "success: balance is now " << balance_ << endl;
        result = n;
        }
      else
        {
        cout << "failure: balance left at " << balance_ << endl;
        }
      os_this_thread::sleep( 2 );
      cout << "write unlock account" << endl;
      lock_.write_unlock();
      return result;
      }

  private:
    os_read_write_semaphore lock_;
    int balance_;
  };

account shared_account; // Global shared account.

void*
writer( void* args )
  {
  shared_account.deposit( 50 );
  os_this_thread::sleep( 1 );
  shared_account.withdraw( 80 );
  return 0;
  }

void*
reader( void* args )
  {
  int balance = shared_account.balance();
  cout << "balance is " << balance << endl;
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;

  os_thread a( writer ); // Spawn new thread.
  os_thread b( reader ); // Spawn new thread.
  os_thread c( writer ); // Spawn new thread.
  os_thread d( reader ); // Spawn new thread.

  os_this_thread::wait_for_thread( a );
  os_this_thread::wait_for_thread( b );
  }

write locked by thread 4
increment balance of 0 by 50
Balance is now 50
write unlock account
read locked by thread 6
read locked by thread 8
read unlock account
balance is 50
read unlock account
balance is 50
write locked by thread 7
increment balance of 50 by 50
Balance is now 100
write unlock account
write locked by thread 4
attempt to withdraw 80 from balance of 100
success: balance is now 20
write unlock account
write locked by thread 7
attempt to withdraw 80 from balance of 20
failure: balance left at 20

event_semaphore

Use an event semaphore to coordinate the actions of several threads based on a single event. For example, in a stockbroking system, you might want several different thread-controlled activities to occur when a stock price changes, depending on the change.

When an event semaphore is pulsed, all threads waiting for the pulse can then proceed.

The following example illustrates the use of event semaphores using a horse race analogy.

The horses wait at the gate until it is opened. When it opens, all the horses move into the gate. Once all the horses have moved into the gate, the starting gun is fired. The firing of the starting gun is the event that starts each horse running.

Example <ospace/thread/examples/event1.cpp>
#include <iostream>
#include <ospace/thread.h>

os_event_semaphore entry_gate;
os_event_semaphore starting_gun;
os_counting_semaphore ready( 0 );

void*
horse( void* args )
  {
  cout << "horse " << os_this_thread::tid() << " trots to gate" << endl;
  os_this_thread::sleep( 1 );
  cout << "horse " << os_this_thread::tid() << " waits for gate" << endl;
  entry_gate.wait();
  cout << "horse " << os_this_thread::tid() << " is ready" << endl;
  ready.post();
  starting_gun.wait();
  cout << "horse " << os_this_thread::tid() << " races" << endl;
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;
  os_thread t1( horse ); // Spawn new thread.
  os_thread t2( horse ); // Spawn new thread.
  os_this_thread::sleep( 2 );
  cout << "open the entry gate" << endl;
  entry_gate.post();
  os_this_thread::sleep( 1 );
  os_thread t3( horse ); // Spawn new thread.
  cout << "wait for all horses" << endl;
  int horses = 3;
  do
    {
    ready.wait();
    --horses;
    }
  while ( horses );

  cout << "shut entry gate" << endl;
  entry_gate.reset();
  cout << "fire the starting gun" << endl;
  starting_gun.pulse();
  os_this_thread::wait_for_thread( t1 );
  os_this_thread::wait_for_thread( t2 );
  os_this_thread::wait_for_thread( t3 );

  cout << "race is over" << endl;
  }

horse 4 trots to gate
horse 6 trots to gate
horse 6 waits for gate
horse 4 waits for gate
open the entry gate
horse 6 is ready
horse 4 is ready
horse 7 trots to gate
wait for all horses
horse 7 waits for gate
horse 7 is ready
shut entry gate
fire the starting gun
horse 6 races
horse 4 races
horse 7 races

condition_mutex

The os_condition_mutex class locks a shared resource, specifically a condition variable, one thread at a time, and depending upon the underlying platform, the lock attempt will fail or the thread will deadlock if it tries to obtain a mutex that it already owns.

The following example is a rewrite of the example Example .

Example <ospace/thread/examples/cond1.cpp>
#include <iostream>
#include <ospace/thread.h>

enum status { open = 0, closed = 1 };

status gate = closed;
os_condition_mutex gate_mutex;
os_condition gate_cond( gate_mutex );


void*
horse( void* )
  {
  cout << "horse " << os_this_thread::tid() << " trots to gate" << endl;
  os_this_thread::sleep( 1 );
  cout << "horse " << os_this_thread::tid() << " waits at gate" << endl;
  gate_mutex.lock();
  // Loop and check the condition. There could be spurious wakeups.
  while ( gate != open )
    gate_cond.wait();
  gate_mutex.unlock();
  cout << "horse " << os_this_thread::tid() << " races" << endl;
  return 0;
  }


int
main()
  {
  os_thread_toolkit initialize;

  // Spawn new threads. Each thread represents a horse.
  os_thread t1( horse );
  os_thread t2( horse );
  os_thread t3( horse );

  os_this_thread::sleep( 3 );

  // Open the gate and signal all the horses.
  gate_mutex.lock();
  gate = open;
  gate_cond.signal_all();
  gate_mutex.unlock();

  os_this_thread::wait_for_thread( t1 );
  os_this_thread::wait_for_thread( t2 );
  os_this_thread::wait_for_thread( t3 );

  cout << "race is over" << endl;
  return 0;
  }

horse 4 trots to gate
horse 6 trots to gate
horse 7 trots to gate
horse 4 waits at gate
horse 6 waits at gate
horse 7 waits at gate
horse 4 races
horse 6 races
horse 7 races
race is over

Copyright©1994-2026 Recursion Software LLC
All Rights Reserved - For use by licensed users only.