JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Multithreaded Programming Guide     Oracle Solaris 11 Express 11/10
search filter icon
search icon

Document Information

Preface

1.  Covering Multithreading Basics

2.  Basic Threads Programming

3.  Thread Attributes

4.  Programming with Synchronization Objects

Mutual Exclusion Lock Attributes

Initializing a Mutex Attribute Object

pthread_mutexattr_init Syntax

pthread_mutexattr_init Return Values

Destroying a Mutex Attribute Object

pthread_mutexattr_destroy Syntax

pthread_mutexattr_destroy Return Values

Setting the Scope of a Mutex

pthread_mutexattr_setpshared Syntax

pthread_mutexattr_setpshared Return Values

Getting the Scope of a Mutex

pthread_mutexattr_getpshared Syntax

pthread_mutexattr_getpshared Return Values

Setting the Mutex Type Attribute

pthread_mutexattr_settype Syntax

pthread_mutexattr_settype Return Values

Getting the Mutex Type Attribute

pthread_mutexattr_gettype Syntax

pthread_mutexattr_gettype Return Values

Setting the Mutex Attribute's Protocol

pthread_mutexattr_setprotocol Syntax

pthread_mutexattr_setprotocol Return Values

Getting the Mutex Attribute's Protocol

pthread_mutexattr_getprotocol Syntax

pthread_mutexattr_getprotocol Return Values

Setting the Mutex Attribute's Priority Ceiling

pthread_mutexattr_setprioceiling Syntax

pthread_mutexattr_setprioceiling Return Values

Getting the Mutex Attribute's Priority Ceiling

pthread_mutexattr_getprioceiling Syntax

pthread_mutexattr_getprioceiling Return Values

Setting the Mutex's Priority Ceiling

pthread_mutex_setprioceiling Syntax

pthread_mutex_setprioceiling Return Values

Getting the Mutex's Priority Ceiling

pthread_mutex_getprioceiling Syntax

pthread_mutex_getprioceiling Return Values

Setting the Mutex's Robust Attribute

pthread_mutexattr_setrobust_np Syntax

pthread_mutexattr_setrobust_np Return Values

Getting the Mutex's Robust Attribute

pthread_mutexattr_getrobust_np Syntax

pthread_mutexattr_getrobust_np Return Values

Using Mutual Exclusion Locks

Initializing a Mutex

pthread_mutex_init Syntax

pthread_mutex_init Return Values

Making a Mutex Consistent

pthread_mutex_consistent_np Syntax

pthread_mutex_consistent_np Return Values

Locking a Mutex

pthread_mutex_lock Syntax

pthread_mutex_lock Return Values

Unlocking a Mutex

pthread_mutex_unlock Syntax

pthread_mutex_unlock Return Values

Locking a Mutex Without Blocking

pthread_mutex_trylock Syntax

pthread_mutex_trylock Return Values

Locking a Mutex Before a Specified Absolute Time

pthread_mutex_timedlock() Syntax

pthread_mutex_timedlock() Return Values

Locking a Mutex Within a Specified Time Interval

pthread_mutex_reltimedlock_np() Syntax

pthread_mutex_reltimedlock_np() Return Values

Destroying a Mutex

pthread_mutex_destroy Syntax

pthread_mutex_destroy Return Values

Code Examples of Mutex Locking

Examples of Using Lock Hierarchies

Examples of Using Nested Locking With a Singly-Linked List

Example of Nested Locking With a Circularly-Linked List

Using Spin Locks

Initializing a Spin Lock

pthread_spin_init() Syntax

pthread_spin_init() Return Values

Acquiring a Spin Lock

pthread_spin_lock() Syntax

pthread_spin_lock() Return Values

Acquiring a Non-Blocking Spin Lock

pthread_spin_trylock() Syntax

pthread_spin_trylock() Return Values

Unlocking a Spin Lock

pthread_spin_unlock() Syntax

pthread_spin_unlock() Return Values

Destroying a Spin Lock

pthread_spin_destroy() Syntax

pthread_spin_destroy() Return Values

Condition Variable Attributes

Initializing a Condition Variable Attribute

pthread_condattr_init Syntax

pthread_condattr_init Return Values

Removing a Condition Variable Attribute

pthread_condattr_destroy Syntax

pthread_condattr_destroy Return Values

Setting the Scope of a Condition Variable

pthread_condattr_setpshared Syntax

pthread_condattr_setpshared Return Values

Getting the Scope of a Condition Variable

pthread_condattr_getpshared Syntax

pthread_condattr_getpshared Return Values

Setting the Clock Selection Condition Variable

pthread_condattr_setclock Syntax

pthread_condattr_setclock Returns

Getting the Clock Selection Condition Variable

pthread_condattr_getclock Syntax

pthread_condattr_getclock Returns

Using Condition Variables

Initializing a Condition Variable

pthread_cond_init Syntax

pthread_cond_init Return Values

Blocking on a Condition Variable

pthread_cond_wait Syntax

pthread_cond_wait Return Values

Unblocking One Thread

pthread_cond_signal Syntax

pthread_cond_signal Return Values

Blocking Until a Specified Time

pthread_cond_timedwait Syntax

pthread_cond_timedwait Return Values

Blocking For a Specified Interval

pthread_cond_reltimedwait_np Syntax

pthread_cond_reltimedwait_np Return Values

Unblocking All Threads

pthread_cond_broadcast Syntax

pthread_cond_broadcast Return Values

Destroying the Condition Variable State

pthread_cond_destroy Syntax

pthread_cond_destroy Return Values

Lost Wake-Up Problem

Producer and Consumer Problem

Synchronization With Semaphores

Named and Unnamed Semaphores

Counting Semaphores Overview

Initializing a Semaphore

sem_init Syntax

Initializing Semaphores With Intraprocess Scope

Initializing Semaphores With Interprocess Scope

sem_init Return Values

Incrementing a Semaphore

sem_post Syntax

sem_post Return Values

Blocking on a Semaphore Count

sem_wait Syntax

sem_wait Return Values

Decrementing a Semaphore Count

sem_trywait Syntax

sem_trywait Return Values

Destroying the Semaphore State

sem_destroy Syntax

sem_destroy Return Values

Producer and Consumer Problem Using Semaphores

Read-Write Lock Attributes

Initializing a Read-Write Lock Attribute

pthread_rwlockattr_init Syntax

pthread_rwlockattr_init Return Values

Destroying a Read-Write Lock Attribute

pthread_rwlockattr_destroy Syntax

pthread_rwlockattr_destroy Return Values

Setting a Read-Write Lock Attribute

pthread_rwlockattr_setpshared Syntax

pthread_rwlockattr_setpshared Return Values

Getting a Read-Write Lock Attribute

pthread_rwlockattr_getpshared Syntax

pthread_rwlockattr_getpshared Return Values

Using Read-Write Locks

Initializing a Read-Write Lock

pthread_rwlock_init Syntax

pthread_rwlock_init Return Values

Acquiring the Read Lock on Read-Write Lock

pthread_rwlock_rdlock Syntax

pthread_rwlock_rdlock Return Values

Acquiring a Read Lock on a Read-Write Lock Before a Specified Absolute Time

pthread_rwlock_timedrdlock Syntax

pthread_rwlock_timedrdlock Return Values

Acquiring a Non-Blocking Read Lock on a Read-Write Lock

pthread_rwlock_tryrdlock Syntax

pthread_rwlock_tryrdlock Return Values

Acquiring the Write Lock on a Read-Write Lock

pthread_rwlock_wrlock Syntax

pthread_rwlock_wrlock Return Values

Acquiring a Non-blocking Write Lock on a Read-Write Lock

pthread_rwlock_trywrlock Syntax

pthread_rwlock_trywrlock Return Values

Acquiring a Write Lock on a Read-Write Lock Before a Specified Absolute Time

pthread_rwlock_timedwrlock Syntax

pthread_rwlock_timedwrlock Returns

Unlocking a Read-Write Lock

pthread_rwlock_unlock Syntax

pthread_rwlock_unlock Return Values

Destroying a Read-Write Lock

pthread_rwlock_destroy Syntax

pthread_rwlock_destroy Return Values

Using Barrier Synchronization

Initializing a Synchronization Barrier

pthread_barrier_init() Syntax

pthread_barrier_init() Return Values

Waiting for Threads to Synchronize at a Barrier

pthread_barrier_wait() Syntax

pthread_barrier_wait() Return Values

Destroying a Synchronization Barrier

pthread_barrier_destroy Syntax

pthread_barrier_destroy Return Values

Initializing a Barrier Attributes Object

pthread_barrierattr_init() Syntax

pthread_barrierattr_init() Return Values

Setting a Barrier Process-Shared Attribute

pthread_barrierattr_setpshared() Syntax

pthread_barrierattr_setpshared() Return Values

Getting a Barrier Process-Shared Attribute

pthread_barrierattr_getpshared() Syntax

pthread_barrierattr_getpshared() Return Values

Destroying a Barrier Attributes Object

pthread_barrierattr_destroy() Syntax

pthread_barrierattr_destroy() Return Values

Synchronization Across Process Boundaries

Producer and Consumer Problem Example

Comparing Primitives

5.  Programming With the Solaris Software

6.  Programming With Solaris Threads

7.  Safe and Unsafe Interfaces

8.  Compiling and Debugging

9.  Programming Guidelines

A.  Extended Example: A Thread Pool Implementation

Index

Using Condition Variables

This section explains how to use condition variables. Table 4-5 lists the functions that are available.

Table 4-5 Condition Variables Functions

Operation
Related Function Description
Initialize a condition variable
Block on a condition variable
Unblock a specific thread
Block until a specified time
Block for a specified interval
Unblock all threads
Destroy condition variable state

Initializing a Condition Variable

Use pthread_cond_init(3C) to initialize the condition variable pointed at by cv to its default value, or to specify condition variable attributes that are already set with pthread_condattr_init().

pthread_cond_init Syntax
int pthread_cond_init(pthread_cond_t *restrict cv,
          const pthread_condattr_t *restrict cattr);
#include <pthread.h>

pthread_cond_t cv;
pthread_condattr_t cattr;
int ret;

/* initialize a condition variable to its default value */
ret = pthread_cond_init(&cv, NULL);

/* initialize a condition variable */
ret = pthread_cond_init(&cv, &cattr); 

The effect of cattr set to NULL is the same as passing the address of a default condition variable attribute object, but without the memory overhead.

Use the macro PTHREAD_COND_INITIALIZER to initialize statically defined condition variables to their default attributes. The PTHREAD_COND_INITIALIZER macro has the same effect as dynamically allocating pthread_cond_init() with null attributes. No error checking is done.

Multiple threads must not simultaneously initialize or reinitialize the same condition variable. If a condition variable is reinitialized or is destroyed, the application must be sure that the condition variable is not in use.

pthread_cond_init Return Values

pthread_cond_init() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.

EINVAL

Description: The value specified by cattr is invalid.

EBUSY

Description: The condition variable is being used.

EAGAIN

Description: The necessary resources are not available.

ENOMEM

Description: Insufficient memory exists to initialize the condition variable.

Blocking on a Condition Variable

Use pthread_cond_wait(3C) to atomically release the mutex pointed to by mp and to cause the calling thread to block on the condition variable pointed to by cv.

pthread_cond_wait Syntax
int pthread_cond_wait(pthread_cond_t *restrict cv,pthread_mutex_t *restrict mutex);
#include <pthread.h>

pthread_cond_t cv;
pthread_mutex_t mp;
int ret;

/* wait on condition variable */
ret = pthread_cond_wait(&cv, &
mp); 

The blocked thread can be awakened by a pthread_cond_signal() , a pthread_cond_broadcast(), or when interrupted by delivery of a signal.

Any change in the value of a condition that is associated with the condition variable cannot be inferred by the return of pthread_cond_wait(). Such conditions must be reevaluated.

The pthread_cond_wait() routine always returns with the mutex locked and owned by the calling thread, even when returning an error.

This function blocks until the condition is signaled. The function atomically releases the associated mutex lock before blocking, and atomically acquires the mutex again before returning.

In typical use, a condition expression is evaluated under the protection of a mutex lock. When the condition expression is false, the thread blocks on the condition variable. The condition variable is then signaled by another thread when the thread changes the condition value. The change causes at least one thread that is waiting on the condition variable to unblock and to reacquire the mutex.

The condition that caused the wait must be retested before continuing execution from the point of the pthread_cond_wait(). The condition could change before an awakened thread reacquires the mutes and returns from pthread_cond_wait(). A waiting thread could be awakened spuriously. The recommended test method is to write the condition check as a while() loop that calls pthread_cond_wait().

    pthread_mutex_lock();
        while(condition_is_false)
            pthread_cond_wait();
    pthread_mutex_unlock();

The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.


Note - pthread_cond_wait() is a cancellation point. If a cancel is pending and the calling thread has cancellation enabled, the thread terminates and begins executing its cleanup handlers while continuing to hold the lock.


pthread_cond_wait Return Values

pthread_cond_wait() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.

EINVAL

Description: The value specified by cv or mp is invalid.

Unblocking One Thread

Use pthread_cond_signal(3C) to unblock one thread that is blocked on the condition variable pointed to by cv.

pthread_cond_signal Syntax
int pthread_cond_signal(pthread_cond_t *cv);
#include <pthread.h>

pthread_cond_t cv;
int ret;

/* one condition variable is signaled */
ret = pthread_cond_signal(&cv); 

Modify the associated condition under the protection of the same mutex used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait.

The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.

When no threads are blocked on the condition variable, calling pthread_cond_signal() has no effect.

Example 4-8 Using pthread_cond_wait() and pthread_cond_signal()

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;

decrement_count()
{
    pthread_mutex_lock(&count_lock);
    while (count == 0)
        pthread_cond_wait(&count_nonzero, &count_lock);
    count = count - 1;
    pthread_mutex_unlock(&count_lock);
}

increment_count()
{
    pthread_mutex_lock(&count_lock);
    if (count == 0)
        pthread_cond_signal(&count_nonzero);
    count = count + 1;
    pthread_mutex_unlock(&count_lock);
}
pthread_cond_signal Return Values

pthread_cond_signal() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.

EINVAL

Description: cv points to an illegal address.

Example 4-8 shows how to use pthread_cond_wait() and pthread_cond_signal().

Blocking Until a Specified Time

Use pthread_cond_timedwait(3C) as you would use pthread_cond_wait(), except that pthread_cond_timedwait() does not block past the time of day specified by abstime .

pthread_cond_timedwait Syntax
int pthread_cond_timedwait(pthread_cond_t *restrict cv,
          pthread_mutex_t *restrict mp, 
          const struct timespec *restrict abstime);
#include <pthread.h>
#include <time.h>

pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t abstime;
int ret;

/* wait on condition variable */
ret = pthread_cond_timedwait(&cv, &
mp, &abstime); 

pthread_cond_timedwait() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_timedwait() is returning an error.

The pthread_cond_timedwait() function blocks until the condition is signaled or until the time of day specified by the last argument has passed.


Note - pthread_cond_timedwait() is also a cancellation point.


Example 4-9 Timed Condition Wait

pthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
clock_gettime(CLOCK_REALTIME, &to);
to.tv_sec += TIMEOUT;
while (cond == FALSE) {
    err = pthread_cond_timedwait(&c, &m, &to);
    if (err == ETIMEDOUT) {
        /* timeout, do something */
        break;
    }
}
pthread_mutex_unlock(&m);
pthread_cond_timedwait Return Values

pthread_cond_timedwait() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value.

EINVAL

Description: cv, mp, or abstime points to an illegal address.

EINVAL

Description: Different mutexes were supplied for concurrent pthread_cond_timedwait() operations on the same condition variable.

ETIMEDOUT

Description: The time specified by abstime has passed.

EPERM

Description: The mutex was not owned by the current thread at the time of the call.

The timeout is specified as a time of day so that the condition can be retested efficiently without recomputing the value, as shown in Example 4-9.

Blocking For a Specified Interval

Use pthread_cond_reltimedwait_np(3C) as you would use pthread_cond_timedwait() with one exception. pthread_cond_reltimedwait_np() takes a relative time interval rather than an absolute future time of day as the value of its last argument.

pthread_cond_reltimedwait_np Syntax
int pthread_cond_reltimedwait_np(pthread_cond_t *cv, 
           pthread_mutex_t *mp, 
          const struct timespec *reltime);
#include <pthread.h>
#include <time.h> 
pthread_cond_t cv; 
pthread_mutex_t mp; 
timestruct_t reltime; 
int ret; 

/* wait on condition variable */ 
ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime); 

pthread_cond_reltimedwait_np() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_reltimedwait_np() is returning an error. The pthread_cond_reltimedwait_np() function blocks until the condition is signaled or until the time interval specified by the last argument has elapsed.


Note - pthread_cond_reltimedwait_np() is also a cancellation point.


pthread_cond_reltimedwait_np Return Values

pthread_cond_reltimedwait_np() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value.

EINVAL

Description: The value specified by reltime is invalid.

ETIMEDOUT

Description: The time interval specified by reltime has passed.

Unblocking All Threads

Use pthread_cond_broadcast(3C) to unblock all threads that are blocked on the condition variable pointed to by cv, specified by pthread_cond_wait().

pthread_cond_broadcast Syntax
int pthread_cond_broadcast(pthread_cond_t *cv);
#include <pthread.h>

pthread_cond_t cv;
int ret;

/* all condition variables are signaled */
ret = pthread_cond_broadcast(&cv); 

When no threads are blocked on the condition variable, pthread_cond_broadcast() has no effect.

Since pthread_cond_broadcast() causes all threads blocked on the condition to contend again for the mutex lock, use pthread_cond_broadcast() with care. For example, use pthread_cond_broadcast() to allow threads to contend for varying resource amounts when resources are freed, as shown in Example 4-10.

Example 4-10 Condition Variable Broadcast

pthread_mutex_t rsrc_lock;
pthread_cond_t rsrc_add;
unsigned int resources;

get_resources(int amount)
{
    pthread_mutex_lock(&rsrc_lock);
    while (resources < amount) {
        pthread_cond_wait(&rsrc_add, &rsrc_lock);
    }
    resources -= amount;
    pthread_mutex_unlock(&rsrc_lock);
}

add_resources(int amount)
{
    pthread_mutex_lock(&rsrc_lock);
    resources += amount;
    pthread_cond_broadcast(&rsrc_add);
    pthread_mutex_unlock(&rsrc_lock);
}

Note that in add_resources() whether resources are updated first, or if pthread_cond_broadcast() is called first inside the mutex lock does not matter.

Modify the associated condition under the protection of the same mutex that is used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait.

pthread_cond_broadcast Return Values

pthread_cond_broadcast() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.

EINVAL

Description: cv points to an illegal address.

Destroying the Condition Variable State

Use pthread_cond_destroy(3C) to destroy any state that is associated with the condition variable pointed to by cv.

pthread_cond_destroy Syntax
int pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h>

pthread_cond_t cv;
int ret;

/* Condition variable is destroyed */
ret = pthread_cond_destroy(&cv); 

Note that the space for storing the condition variable is not freed.

pthread_cond_destroy Return Values

pthread_cond_destroy() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.

EINVAL

Description: The value specified by cv is invalid.

Lost Wake-Up Problem

A call to pthread_cond_signal() or pthread_cond_broadcast() when the thread does not hold the mutex lock associated with the condition can lead to lost wake-up bugs.

A lost wake-up occurs when all of the following conditions are in effect:

This can occur only if the condition being tested is modified without holding the mutex lock associated with the condition. As long as the condition being tested is modified only while holding the associated mutex, pthread_cond_signal() and pthread_cond_broadcast() can be called regardless of whether they are holding the associated mutex.

Producer and Consumer Problem

The producer and consumer problem is one of the small collection of standard, well-known problems in concurrent programming. A finite-size buffer and two classes of threads, producers and consumers, put items into the buffer (producers) and take items out of the buffer (consumers).

A producer cannot put something in the buffer until the buffer has space available. A consumer cannot take something out of the buffer until the producer has written to the buffer.

A condition variable represents a queue of threads that wait for some condition to be signaled.

Example 4-11 has two such queues. One (less) queue for producers waits for a slot in the buffer. The other (more) queue for consumers waits for a buffer slot containing information. The example also has a mutex, as the data structure describing the buffer must be accessed by only one thread at a time.

Example 4-11 Producer and Consumer Problem With Condition Variables

typedef struct { 
     char buf[BSIZE]; 
     int occupied; 
     int nextin; 
     int nextout; 
     pthread_mutex_t mutex; 
     pthread_cond_t more; 
     pthread_cond_t less; } 
buffer_t; 

buffer_t buffer;

As Example 4-12 shows, the producer thread acquires the mutex protecting the buffer data structure. The producer thread then makes certain that space is available for the item produced. If space is not available, the producer thread calls pthread_cond_wait() . pthread_cond_wait() causes the producer thread to join the queue of threads that are waiting for the condition less to be signaled. less represents available room in the buffer.

At the same time, as part of the call to pthread_cond_wait(), the thread releases its lock on the mutex. The waiting producer threads depend on consumer threads to signal when the condition is true, as shown in Example 4-12. When the condition is signaled, the first thread waiting on less is awakened. However, before the thread can return from pthread_cond_wait(), the thread must acquire the lock on the mutex again.

Acquire the mutex to ensure that the thread again has mutually exclusive access to the buffer data structure. The thread then must check that available room in the buffer actually exists. If room is available, the thread writes into the next available slot.

At the same time, consumer threads might be waiting for items to appear in the buffer. These threads are waiting on the condition variable more . A producer thread, having just deposited something in the buffer, calls pthread_cond_signal() to wake up the next waiting consumer. If no consumers are waiting, this call has no effect.

Finally, the producer thread unlocks the mutex, allowing other threads to operate on the buffer data structure.

Example 4-12 The Producer and Consumer Problem: the Producer

void producer(buffer_t *b, char item)
{
    pthread_mutex_lock(&b->mutex);
   
    while (b->occupied >= BSIZE)
        pthread_cond_wait(&b->less, &b->mutex);

    assert(b->occupied < BSIZE);

    b->buf[b->nextin++] = item;

    b->nextin %= BSIZE;
    b->occupied++;

    /* now: either b->occupied < BSIZE and b->nextin is the index
       of the next empty slot in the buffer, or
       b->occupied == BSIZE and b->nextin is the index of the
       next (occupied) slot that will be emptied by a consumer
       (such as b->nextin == b->nextout) */

    pthread_cond_signal(&b->more);

    pthread_mutex_unlock(&b->mutex);
}

Note the use of the assert() statement. Unless the code is compiled with NDEBUG defined, assert() does nothing when its argument evaluates to true (nonzero). The program aborts if the argument evaluates to false (zero). Such assertions are especially useful in multithreaded programs. assert() immediately points out runtime problems if the assertion fails. assert() has the additional effect of providing useful comments.

The comment that begins /* now: either b->occupied ... could better be expressed as an assertion, but the statement is too complicated as a Boolean-valued expression and so is given in English.

Both assertions and comments are examples of invariants. These invariants are logical statements that should not be falsified by the execution of the program with the following exception. The exception occurs during brief moments when a thread is modifying some of the program variables mentioned in the invariant. An assertion, of course, should be true whenever any thread executes the statement.

The use of invariants is an extremely useful technique. Even if the invariants are not stated in the program text, think in terms of invariants when you analyze a program.

The invariant in the producer code that is expressed as a comment is always true whenever a thread executes the code where the comment appears. If you move this comment to just after the mutex_unlock(), the comment does not necessarily remain true. If you move this comment to just after the assert() , the comment is still true.

This invariant therefore expresses a property that is true at all times with the following exception. The exception occurs when either a producer or a consumer is changing the state of the buffer. While a thread is operating on the buffer under the protection of a mutex, the thread might temporarily falsify the invariant. However, once the thread is finished, the invariant should be true again.

Example 4-13 shows the code for the consumer. The logic flow is symmetric with the logic flow of the producer.

Example 4-13 The Producer and Consumer Problem: the Consumer

char consumer(buffer_t *b)
{
    char item;
    pthread_mutex_lock(&b->mutex);
    while(b->occupied <= 0)
        pthread_cond_wait(&b->more, &b->mutex);

    assert(b->occupied > 0);

    item = b->buf[b->nextout++];
    b->nextout %= BSIZE;
    b->occupied--;

    /* now: either b->occupied > 0 and b->nextout is the index
       of the next occupied slot in the buffer, or
       b->occupied == 0 and b->nextout is the index of the next
       (empty) slot that will be filled by a producer (such as
       b->nextout == b->nextin) */

    pthread_cond_signal(&b->less);
    pthread_mutex_unlock(&b->mutex);

    return(item);
}