Multithreaded Programming Guide

Chapter 4 Programming with Synchronization Objects

This chapter describes the synchronization types available with threads and discusses when and how to use synchronization.

Synchronization objects are variables in memory that you access just like data. Threads in different processes can communicate with each other through synchronization objects placed in threads-controlled shared memory, even though the threads in different processes are generally invisible to each other.

Synchronization objects can also be placed in files and can have lifetimes beyond that of the creating process.

The available types of synchronization objects are:

Here are situations that can benefit from the use of synchronization:


Note –

On 32-bit architectures a long long is not atomic [An atomic operation cannot be divided into smaller operations.] and is read and written as two 32-bit quantities. The types int, char, float, and pointers are atomic on SPARC Platform Edition machines and Intel Architecture machines.


Mutual Exclusion Lock Attributes

Use mutual exclusion locks (mutexes) to serialize thread execution. Mutual exclusion locks synchronize threads, usually by ensuring that only one thread at a time executes a critical section of code. Mutex locks can also preserve single-threaded code.

To change the default mutex attributes, you can declare and initialize an attribute object. Often, the mutex attributes are set in one place at the beginning of the application so they can be located quickly and modified easily. Table 4–1 lists the functions discussed in this section that manipulate mutex attributes.

Table 4–1 Mutex Attributes Routines

Operation 

Destination Discussion 

 

Initialize a mutex attribute object 

pthread_mutexattr_init(3THR)

 

Destroy a mutex attribute object 

pthread_mutexattr_destroy(3THR)

 

Set the scope of a mutex 

pthread_mutexattr_setpshared(3THR)

 

Get the scope of a mutex 

pthread_mutexattr_getpshared(3THR)

 

Set the mutex type attribute 

pthread_mutexattr_settype(3THR)

 

Get the mutex type attribute 

pthread_mutexattr_gettype(3THR)

 

Set mutex attribute's protocol 

pthread_mutexattr_setprotocol(3THR)

 

Get mutex attribute's protocol 

pthread_mutexattr_getprotocol(3THR)

 

Set mutex attribute's priority ceiling 

pthread_mutexattr_setprioceiling(3THR)

 

Get mutex attribute's priority ceiling 

pthread_mutexattr_getprioceiling(3THR)

 

Set mutex's priority ceiling 

pthread_mutex_setprioceiling(3THR)

 

Get mutex's priority ceiling 

pthread_mutex_getprioceiling(3THR)

 

Set mutex's robust attribute 

pthread_mutexattr_setrobust_np(3THR)

 

Get mutex's robust attribute 

pthread_mutexattr_getrobust_np(3THR)

 

The differences between Solaris threads and POSIX threads, when defining the scope of a mutex, are shown in Table 4–2.

Table 4–2 Mutex Scope Comparison

Solaris 

POSIX 

Definition 

USYNC_PROCESS

PTHREAD_PROCESS_SHARED

Use to synchronize threads in this and other processes 

USYNC_PROCESS_ROBUST

No POSIX equivalent 

Use to robustly synchronize threads between processes

USYNC_THREAD

PTHREAD_PROCESS_PRIVATE

Use to synchronize threads in this process only 

Initialize a Mutex Attribute Object

pthread_mutexattr_init(3THR)

Use pthread_mutexattr_init(3THR) to initialize attributes associated with this object to their default values. Storage for each attribute object is allocated by the threads system during execution.

The default value of the pshared attribute when this function is called is PTHREAD_PROCESS_PRIVATE, which means that the initialized mutex can be used within a process.

Prototype:
int	pthread_mutexattr_init(pthread_mutexattr_t *mattr);
#include <pthread.h>

pthread_mutexattr_t mattr;
int ret;

/* initialize an attribute to default value */
ret = pthread_mutexattr_init(&mattr); 

mattr is an opaque type that contains a system-allocated attribute object. The possible values of mattr's scope are PTHREAD_PROCESS_PRIVATE (the default) and PTHREAD_PROCESS_SHARED.

Before a mutex attribute object can be reinitialized, it must first be destroyed by a call to pthread_mutexattr_destroy(3THR). The pthread_mutexattr_init() call results in the allocation of an opaque object. If the object is not destroyed, a memory leak will result.

Return Values

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


ENOMEM

There is not enough memory to initialize the mutex attributes object.

Destroy a Mutex Attribute Object

pthread_mutexattr_destroy(3THR)

pthread_mutexattr_destroy(3THR) deallocates the storage space used to maintain the attribute object created by pthread_mutexattr_init().

Prototype:
int	pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
#include <pthread.h>

pthread_mutexattr_t mattr;
int ret;

/* destroy an attribute */
ret = pthread_mutexattr_destroy(&mattr); 

Return Values

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


EINVAL

The value specified by mattr is invalid.

Set the Scope of a Mutex

pthread_mutexattr_setpshared(3THR)

pthread_mutexattr_setpshared(3THR) sets the scope of the mutex variable.

The scope of a mutex variable can be either process private (intraprocess) or system wide (interprocess). If the mutex is created with the pshared attribute set to the PTHREAD_PROCESS_SHARED state, and it exists in shared memory, it can be shared among threads from more than one process. This is equivalent to the USYNC_PROCESS flag in mutex_init() in the original Solaris threads.

Prototype:
int	pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr,
    int pshared);
#include <pthread.h>

pthread_mutexattr_t mattr;
int ret;

ret = pthread_mutexattr_init(&mattr);
/*
 * resetting to its default value: private
 */
ret = pthread_mutexattr_setpshared(&mattr,
     PTHREAD_PROCESS_PRIVATE);

If the mutex pshared attribute is set to PTHREAD_PROCESS_PRIVATE, only those threads created by the same process can operate on the mutex.

Return Values

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


EINVAL

The value specified by mattr is invalid.

Get the Scope of a Mutex

pthread_mutexattr_getpshared(3THR)

pthread_mutexattr_getpshared(3THR) returns the scope of the mutex variable defined by pthread_mutexattr_setpshared().

Prototype:
int	pthread_mutexattr_getpshared(pthread_mutexattr_t *mattr,
    int *pshared);
#include <pthread.h>

pthread_mutexattr_t mattr;
int pshared, ret;

/* get pshared of mutex */
ret = pthread_mutexattr_getpshared(&mattr, &pshared); 

Get the current value of pshared for the attribute object mattr. It is either PTHREAD_PROCESS_SHARED or PTHREAD_PROCESS_PRIVATE.

Return Values

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


EINVAL

The value specified by mattr is invalid.

Set the Mutex Type Attribute

pthread_mutexattr_settype(3THR)

#include <pthread.h>

int pthread_mutexattr_settype(pthread_mutexattr_t  *attr , int type);

pthread_mutexattr_settype(3THR) sets the mutex type attribute. The default value of the type attribute is PTHREAD_MUTEX_DEFAULT.

The type argument specifies the type of mutex. Valid mutex types include:


PTHREAD_MUTEX_NORMAL

This type of mutex does not detect deadlock. A thread attempting to relock this mutex without first unlocking it will deadlock. Attempting to unlock a mutex locked by a different thread results in undefined behavior. Attempting to unlock an unlocked mutex results in undefined behavior.


PTHREAD_MUTEX_ERRORCHECK

This type of mutex provides error checking. A thread attempting to relock this mutex without first unlocking it will return with an error. A thread attempting to unlock a mutex which another thread has locked will return with an error. A thread attempting to unlock an unlocked mutex will return with an error.


PTHREAD_MUTEX_RECURSIVE

A thread attempting to relock this mutex without first unlocking it will succeed in locking the mutex. The relocking deadlock which can occur with mutexes of type PTHREAD_MUTEX_NORMAL cannot occur with this type of mutex. Multiple locks of this mutex require the same number of unlocks to release the mutex before another thread can acquire the mutex. A thread attempting to unlock a mutex which another thread has locked will return with an error. A thread attempting to unlock an unlocked mutex will return with an error.


PTHREAD_MUTEX_DEFAULT

Attempting to recursively lock a mutex of this type results in undefined behavior. Attempting to unlock a mutex of this type which was not locked by the calling thread results in undefined behavior. Attempting to unlock a mutex of this type which is not locked results in undefined behavior. An implementation is allowed to map this mutex to one of the other mutex types. (For Solaris threads, PTHREAD_PROCESS_DEFAULT is mapped to PTHREAD_PROCESS_NORMAL.)

Return Values

If successful, the pthread_mutexattr_settype function returns zero. Otherwise, an error number is returned to indicate the error.


EINVAL

The value type is invalid.


EINVAL

The value specified by attr is invalid.

Get the Mutex Type Attribute

pthread_mutexattr_gettype(3THR)

#include <pthread.h>

int pthread_mutexattr_gettype(pthread_mutexattr_t  *attr , int  *type);

pthread_mutexattr_gettype(3THR) gets the mutex type attribute set by pthread_mutexattr_settype(). The default value of the type attribute is PTHREAD_MUTEX_DEFAULT.

The type argument specifies the type of mutex. Valid mutex types include:

For a description of each type, see pthread_mutexattr_settype(3THR).

Set Mutex Attribute's Protocol

pthread_mutexattr_setprotocol(3THR)

pthread_mutexattr_setprotocol(3THR) sets the protocol attribute of a mutex attribute object.

#include <pthread.h>

int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);

attr points to a mutex attribute object created by an earlier call to pthread_mutexattr_init().

protocol defines the protocol applied to the mutex attribute object.

The value of protocol, defined in pthread.h, can be: PTHREAD_PRIO_NONE, PTHREAD_PRIO_INHERIT, or PTHREAD_PRIO_PROTECT.

When a thread owns a mutex that is initialized with PTHREAD_PRIO_INHERIT or PTHREAD_PRIO_PROTECT, and that thread's original priority changes, such as by a call to sched_setparam(), the scheduler does not move the thread to the tail of the scheduling queue at it's new priority. Similarly, when a thread unlocks a mutex that is initialized with PTHREAD_PRIO_INHERIT or PTHREAD_PRIO_PROTECT, and that thread's original priority changes, the scheduler does not move the thread to the tail of the scheduling queue at it's new priority.

If a thread simultaneously owns several mutexes initialized with a mix of PTHREAD_PRIO_INHERIT and PTHREAD_PRIO_PROTECT, it executes at the highest priority obtained by either of these protocols.

Return Values

On successful completion, pthread_mutexattr_setprotocol() returns 0. Any other return value indicates that an error occurred.

If either of the following conditions occurs, pthread_mutexattr_setprotocol() fails and returns the corresponding value.


ENOSYS

Neither of the options _POSIX_THREAD_PRIO_INHERIT and _POSIX_THREAD_PRIO_PROTECT is defined and the implementation does not support the function.


ENOTSUP

The value specified by protocol is an unsupported value.

If either of the following conditions occurs, pthread_mutexattr_setprotocol() might fail and return the corresponding value.


EINVAL

The value specified by attr or protocol is not valid.


EPERM

The caller does not have the privilege to perform the operation.

Get Mutex Attribute's Protocol

pthread_mutexattr_getprotocol(3THR)

pthread_mutexattr_getprotocol(3THR) gets the protocol attribute of a mutex attribute object.

#include <pthread.h>

int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, 
											int *protocol);

attr points to a mutex attribute object created by an earlier call to pthread_mutexattr_init().

protocol contains the protocol attribute: PTHREAD_PRIO_NONE, PTHREAD_PRIO_INHERIT, or PTHREAD_PRIO_PROTECT.

Return Values

On successful completion, pthread_mutexattr_getprotocol() returns 0. Any other return value indicates that an error occurred.

If the following condition occurs, pthread_mutexattr_getprotocol() fails and returns the corresponding value.


ENOSYS

Neither of the options, _POSIX_THREAD_PRIO_INHERIT nor _POSIX_THREAD_PRIO_PROTECT is defined and the implementation does not support the function.

If either of the following conditions occurs, pthread_mutexattr_getprotocol() might fail and return the corresponding value.


EINVAL

The value specified by attr is invalid.


EPERM

The caller does not have the privilege to perform the operation.

Set Mutex Attribute's Priority Ceiling

pthread_mutexattr_setprioceiling(3THR)

pthread_mutexattr_setprioceiling(3THR) sets the priority ceiling attribute of a mutex attribute object.

#include <pthread.h>

int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, 
												int prioceiling, 
												int *oldceiling);

attr points to a mutex attribute object created by an earlier call to pthread_mutexattr_init().

prioceiling specifies the priority ceiling of initialized mutexes. The ceiling defines the minimum priority level at which the critical section guarded by the mutex is executed. prioceiling will be within the maximum range of priorities defined by SCHED_FIFO. To avoid priority inversion, prioceiling will be set to a priority higher than or equal to the highest priority of all the threads that might lock the particular mutex.

oldceiling contains the old priority ceiling value.

Return Values

On successful completion, pthread_mutexattr_setprioceiling() returns 0. Any other return value indicates that an error occurred.

If any of the following conditions occurs, pthread_mutexattr_setprioceiling() fails and returns the corresponding value.


ENOSYS

The option _POSIX_THREAD_PRIO_PROTECT is not defined and the implementation does not support the function.

If either of the following conditions occurs, pthread_mutexattr_setprioceiling() might fail and return the corresponding value.


EINVAL

The value specified by attr or prioceiling is invalid.


EPERM

The caller does not have the privilege to perform the operation.

Get Mutex Attribute's Priority Ceiling

pthread_mutexattr_getprioceiling(3THR)

pthread_mutexattr_getprioceiling(3THR) gets the priority ceiling attribute of a mutex attribute object.

#include <pthread.h>

int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *attr, 
												int *prioceiling);

attr designates the attribute object created by an earlier call to pthread_mutexattr_init().


Note –

The attr mutex attribute object includes the priority ceiling attribute only if the symbol _POSIX_THREAD_PRIO_PROTECT is defined.


pthread_mutexattr_getprioceiling() returns the priority ceiling of initialized mutexes in prioceiling. The ceiling defines the minimum priority level at which the critical section guarded by the mutex is executed. prioceiling will be within the maximum range of priorities defined by SCHED_FIFO. To avoid priority inversion, prioceiling will be set to a priority higher than or equal to the highest priority of all the threads that might lock the particular mutex.

Return Values

On successful completion, pthread_mutexattr_getprioceiling() returns 0. Any other return value indicates that an error occurred.

If any of the following conditions occurs, pthread_mutexattr_getprioceiling() fails and returns the corresponding value.


ENOSYS

The option _POSIX_THREAD_PRIO_PROTECT is not defined and the implementation does not support the function.

If either of the following conditions occurs, pthread_mutexattr_getprioceiling() might fail and return the corresponding value.


EINVAL

The value specified by attr is invalid.


EPERM

The caller does not have the privilege to perform the operation.

Set Mutex's Priority Ceiling

pthread_mutex_setprioceiling(3THR)

pthread_mutex_setprioceiling(3THR) sets the priority ceiling of a mutex.

#include <pthread.h>

int pthread_mutex_setprioceiling(pthread_mutex_t *mutex, 
											int prioceiling, 
											int *old_ceiling);

pthread_mutex_setprioceiling() changes the priority ceiling, prioceiling, of a mutex, mutex. pthread_mutex_setprioceiling() locks the mutex if it is unlocked, or blocks until it can successfully lock the mutex, changes the priority ceiling of the mutex and releases the mutex. The process of locking the mutex need not adhere to the priority protect protocol.

If pthread_mutex_setprioceiling() succeeds, the previous value of the priority ceiling is returned in old_ceiling. If pthread_mutex_setprioceiling() fails, the mutex priority ceiling remains unchanged.

Return Values

On successful completion, pthread_mutex_setprioceiling() returns 0. Any other return value indicates that an error occurred.

If the following condition occurs, pthread_mutexatt_setprioceiling() fails and returns the corresponding value.


ENOSYS

The option _POSIX_THREAD_PRIO_PROTECT is not defined and the implementation does not support the function.

If any of the following conditions occurs, pthread_mutex_setprioceiling() might fail and return the corresponding value.


EINVAL

The priority requested by prioceiling is out of range.


EINVAL

The value specified by mutex does not refer to a currently existing mutex.


ENOSYS

The implementation does not support the priority ceiling protocol for mutexes.


EPERM

The caller does not have the privilege to perform the operation.

Get Mutex's Priority Ceiling

pthread_mutex_getprioceiling(3THR)

pthread_mutex_getprioceiling(3THR) gets the priority ceiling of a mutex.

#include <pthread.h>

int pthread_mutex_getprioceiling(const pthread_mutex_t *mutex, 
											int *prioceiling);

pthread_mutex_getprioceiling() returns the priority ceiling, prioceiling of a mutex mutex.

Return Values

On successful completion, pthread_mutex_getprioceiling() returns 0. Any other return value indicates that an error occurred.

If any of the following conditions occurs, pthread_mutexatt_getprioceiling() fails and returns the corresponding value.


ENOSYS

The option _POSIX_THREAD_PRIO_PROTECT is not defined and the implementation does not support the function.

If any of the following conditions occurs, pthread_mutex_getprioceiling() might fail and return the corresponding value.


EINVAL

The value specified by mutex does not refer to a currently existing mutex.


ENOSYS

The implementation does not support the priority ceiling protocol for mutexes.


EPERM

The caller does not have the privilege to perform the operation.

Set Mutex's Robust Attribute

pthread_mutexattr_setrobust_np(3THR)

pthread_mutexattr_setrobust_np(3THR) sets the robust attribute of a mutex attribute object.

#include <pthread.h>

int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, 
												int *robustness);

Note –

pthread_mutexattr_setrobust_np() applies only if the symbol _POSIX_THREAD_PRIO_INHERIT is defined.


attr points to the mutex attribute object previously created by a call to pthread_mutexattr_init().

robustness defines the behavior when the owner of the mutex dies. The value of robustness, defined in pthread.h, is PTHREAD_MUTEX_ROBUST_NP or PTHREAD_MUTEX_STALLED_NP. The default value is PTHREAD_MUTEX_STALLED_NP.

Return Values

On successful completion, pthread_mutexattr_setrobust_np() returns 0. Any other return value indicates that an error occurred.

If any of the following conditions occurs, pthread_mutexattr_setrobust_np() fails and returns the corresponding value.


ENOSYS

The option _POSIX_THREAD_PRIO__INHERIT is not defined or the implementation does not support pthread_mutexattr_setrobust_np().


ENOTSUP

The value specified by robustness is not supported.

pthread_mutexattr_setrobust_np() might fail if:


EINVAL

The value specified by attr or robustness is invalid.

Get Mutex's Robust Attribute

pthread_mutexattr_getrobust_np(3THR)

pthread_mutexattr_getrobust_np(3THR) gets the robust attribute of a mutex attribute object.

#include <pthread.h>

int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, 
												int *robustness);

Note –

pthread_mutexattr_getrobust_np() applies only if the symbol _POSIX_THREAD_PRIO_INHERIT is defined.


attr points to the mutex attribute object previously created by a call to pthread_mutexattr_init().

robustness is the value of the robust attribute of a mutex attribute object.

Return Values

On successful completion, pthread_mutexattr_getrobust_np() returns 0. Any other return value indicates that an error occurred.

If any of the following conditions occurs, pthread_mutexattr_getrobust_np() fails and returns the corresponding value.


ENOSYS

The option _POSIX_THREAD_PRIO__INHERIT is not defined or the implementation does not support pthread_mutexattr_getrobust_np().

pthread_mutexattr_getrobust_np() might fail if:


EINVAL

The value specified by attr or robustness is invalid.

Using Mutual Exclusion Locks

Table 4–3 lists the functions discussed in this chapter that manipulate mutex locks.

Table 4–3 Routines for Mutual Exclusion Locks

Operation 

Destination Discussion 

 

Initialize a mutex 

pthread_mutex_init(3THR)

 

Make mutex consistent 

pthread_mutex_consistent_np(3THR)

 

Lock a mutex 

pthread_mutex_lock(3THR)

 

Unlock a mutex 

pthread_mutex_unlock(3THR)

 

Lock with a nonblocking mutex 

pthread_mutex_trylock(3THR)

 

Destroy a mutex 

pthread_mutex_destroy(3THR)

 

The default scheduling policy, SCHED_OTHER, does not specify the order in which threads can acquire a lock. When multiple threads are waiting for a mutex, the order of acquisition is undefined. When there is contention, the default behavior is to unblock threads in priority order.

Initialize a Mutex

pthread_mutex_init(3THR)

Use pthread_mutex_init(3THR) to initialize the mutex pointed at by mp to its default value (mattr is NULL), or to specify mutex attributes that have already been set with pthread_mutexattr_init(). (For Solaris threads, see mutex_init(3THR).)

Prototype:
int	pthread_mutex_init(pthread_mutex_t *mp,
    const pthread_mutexattr_t *mattr);
#include <pthread.h>

pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER;
pthread_mutexattr_t mattr;
int ret;

/* initialize a mutex to its default value */
ret = pthread_mutex_init(&mp, NULL);

/* initialize a mutex */
ret = pthread_mutex_init(&mp, &mattr); 

When the mutex is initialized, it is in an unlocked state. The mutex can be in memory shared between processes or in memory private to a process.


Note –

The mutex memory must be cleared to zero before initialization.


The effect of mattr being NULL is the same as passing the address of a default mutex attribute object, but without the memory overhead.

Statically defined mutexes can be initialized directly to have default attributes with the macro PTHREAD_MUTEX_INITIALIZER.

A mutex lock must not be reinitialized or destroyed while other threads might be using it. Program failure will result if either action is not done correctly. If a mutex is reinitialized or destroyed, the application must be sure the mutex is not currently in use.

Return Values

pthread_mutex_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.


EBUSY

The implementation has detected an attempt to reinitialize the object referenced by mp (a previously initialized, but not yet destroyed mutex).


EINVAL

The mattr attribute value is invalid. The mutex has not been modified.


EFAULT

The address for the mutex pointed at by mp is invalid.

Make Mutex Consistent

pthread_mutex_consistent_np(3THR)

#include <pthread.h>
int	pthread_mutex_consistent_np(pthread_mutex_t *mutex); 

Note –

pthread_mutex_consistent_np() applies only if the symbol _POSIX_THREAD_PRIO_INHERIT is defined and for mutexes that are initialized with the protocol attribute value PTHREAD_PRIO_INHERIT.


If the owner of a mutex dies, the mutex can become inconsistent.

pthread_mutex_consistent_np makes the mutex object, mutex, consistent after the death of its owner.

Call pthread_mutex_lock() to acquire the inconsistent mutex. The EOWNWERDEAD return value indicates an inconsistent mutex.

Call pthread_mutex_consistent_np() while holding the mutex acquired by a previous call to pthread_mutex_lock().

Because the critical section protected by the mutex might have been left in an inconsistent state by the dead owner, make the mutex consistent only if you are able to make the critical section protected by the mutex consistent.

Calls to pthread_mutex_lock(), pthread_mutex_unlock(), and pthread_mutex_trylock() for a consistent mutex behave in the normal manner.

The behavior of pthread_mutex_consistent_np() for a mutex that is not inconsistent, or that is not held, is undefined.

Return Values

pthread_mutex_consistent_np() 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.

pthread_mutex_consistent_np() fails if:


ENOSYS

The option _POSIX_THREAD_PRIO_INHERIT is not defined or the implementation does not support pthread_mutex_consistent_np().

pthread_mutex_consistent_np() might fail if:


EINVAL

The value specified by mutex is invalid.

Lock a Mutex

pthread_mutex_lock(3THR)

Prototype:
int	pthread_mutex_lock(pthread_mutex_t *mutex); 
#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_ mutex_lock(&mp); /* acquire the mutex */

Use pthread_mutex_lock(3THR) to lock the mutex pointed to by mutex. When pthread_mutex_lock() returns, the mutex is locked and the calling thread is the owner. If the mutex is already locked and owned by another thread, the calling thread blocks until the mutex becomes available. (For Solaris threads, see mutex_lock(3THR).)

If the mutex type is PTHREAD_MUTEX_NORMAL, deadlock detection is not provided. Attempting to relock the mutex causes deadlock. If a thread attempts to unlock a mutex that it has not locked or a mutex that is unlocked, undefined behavior results.

If the mutex type is PTHREAD_MUTEX_ERRORCHECK, then error checking is provided. If a thread attempts to relock a mutex that it has already locked, an error will be returned. If a thread attempts to unlock a mutex that it has not locked or a mutex that is unlocked, an error will be returned.

If the mutex type is PTHREAD_MUTEX_RECURSIVE, then the mutex maintains the concept of a lock count. When a thread successfully acquires a mutex for the first time, the lock count is set to one. Every time a thread relocks this mutex, the lock count is incremented by one. Each time the thread unlocks the mutex, the lock count is decremented by one. When the lock count reaches zero, the mutex becomes available for other threads to acquire. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, an error will be returned.

If the mutex type is PTHREAD_MUTEX_DEFAULT, attempting to recursively lock the mutex results in undefined behavior. Attempting to unlock the mutex if it was not locked by the calling thread results in undefined behavior. Attempting to unlock the mutex if it is not locked results in undefined behavior.

Return Values

pthread_mutex_lock() 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.


EAGAIN

The mutex could not be acquired because the maximum number of recursive locks for mutex has been exceeded.


EDEADLK

The current thread already owns the mutex.

If the symbol _POSIX_THREAD_PRIO_INHERIT is defined, the mutex is initialized with the protocol attribute value PTHREAD_PRIO_INHERIT, and the robustness argument of pthread_mutexattr_setrobust_np() is PTHREAD_MUTEX_ROBUST_NP the function fails and returns:


EOWNERDEAD

The last owner of this mutex died while holding the mutex. This mutex is now owned by the caller. The caller must attempt to make the state protected by the mutex consistent.

If the caller is able to make the state consistent, call pthread_mutex_consistent_np() for the mutex and unlock the mutex. Subsequent calls to pthread_mutex_lock() will behave normally.

If the caller is unable to make the state consistent, do not call pthread_mutex_init() for the mutex, but unlock the mutex. Subsequent calls to pthread_mutex_lock() fail to acquire the mutex and return an ENOTRECOVERABLE error code.

If the owner that acquired the lock with EOWNERDEAD dies, the next owner acquires the lock with EOWNERDEAD.


ENOTRECOVERABLE

The mutex you are trying to acquire is protecting state left irrecoverable by the mutex's previous owner that died while holding the lock. The mutex has not been acquired. This condition can occur when the lock was previously acquired with EOWNERDEAD and the owner was unable to cleanup the state and had unlocked the mutex without making the mutex state consistent.


ENOMEM

The limit on the number of simultaneously held mutexes has been exceeded.

Unlock a Mutex

pthread_mutex_unlock(3THR)

Use pthread_mutex_unlock(3THR) to unlock the mutex pointed to by mutex. (For Solaris threads, see mutex_unlock(3THR).)

Prototype:
int	pthread_mutex_unlock(pthread_mutex_t *mutex); 
#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_mutex_unlock(&mutex); /* release the mutex */

pthread_mutex_unlock() releases the mutex object referenced by mutex. The manner in which a mutex is released is dependent upon the mutex's type attribute. If there are threads blocked on the mutex object referenced by mutex when pthread_mutex_unlock() is called, resulting in the mutex becoming available, the scheduling policy is used to determine which thread shall acquire the mutex. (In the case of PTHREAD_MUTEX_RECURSIVE mutexes, the mutex becomes available when the count reaches zero and the calling thread no longer has any locks on this mutex).

Return Values

pthread_mutex_unlock() 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.


EPERM

The current thread does not own the mutex.

Lock With a Nonblocking Mutex

pthread_mutex_trylock(3THR)

Use pthread_mutex_trylock(3THR) to attempt to lock the mutex pointed to by mutex. (For Solaris threads, see mutex_trylock(3THR).)

Prototype:
int	pthread_mutex_trylock(pthread_mutex_t *mutex); 
#include <pthread.h>

pthread_mutex_t mutex;
int ret;

ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */

pthread_mutex_trylock() is a nonblocking version of pthread_mutex_lock(). If the mutex object referenced by mutex is currently locked (by any thread, including the current thread), the call returns immediately. Otherwise, the mutex is locked and the calling thread is the owner.

Return Values

pthread_mutex_trylock() 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.


EBUSY

The mutex could not be acquired because the mutex pointed to by mutex was already locked.


EAGAIN

The mutex could not be acquired because the maximum number of recursive locks for mutex has been exceeded.

If the symbol _POSIX_THREAD_PRIO_INHERIT is defined, the mutex is initialized with the protocol attribute value PTHREAD_PRIO_INHERIT, and the robustness argument of pthread_mutexattr_setrobust_np() is PTHREAD_MUTEX_ROBUST_NP the function fails and returns:


EOWNERDEAD

The last owner of this mutex died while holding the mutex. This mutex is now owned by the caller. The caller must attempt to make the state protected by the mutex consistent.

If the caller is able to make the state consistent, call pthread_mutex_consistent_np() for the mutex and unlock the mutex. Subsequent calls to pthread_mutex_lock() will behave normally.

If the caller is unable to make the state consistent, do not call pthread_mutex_init() for the mutex, but unlock the mutex. Subsequent calls to pthread_mutex_trylock() fail to acquire the mutex and return an ENOTRECOVERABLE error code.

If the owner that acquired the lock with EOWNERDEAD dies, the next owner acquires the lock with EOWNERDEAD.


ENOTRECOVERABLE

The mutex you are trying to acquire is protecting state left irrecoverable by the mutex's previous owner that died while holding the lock. The mutex has not been acquired. This condition can occur when the lock was previously acquired with EOWNERDEAD and the owner was unable to cleanup the state and had unlocked the mutex without making the mutex state consistent.


ENOMEM

The limit on the number of simultaneously held mutexes has been exceeded.

Destroy a Mutex

pthread_mutex_destroy(3THR)

Use pthread_mutex_destroy(3THR) to destroy any state associated with the mutex pointed to by mp. (For Solaris threads, see mutex_destroy(3THR).)

Prototype:
int	pthread_mutex_destroy(pthread_mutex_t *mp); 
#include <pthread.h>

pthread_mutex_t mp;
int ret;

ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */

Note that the space for storing the mutex is not freed.

Return Values

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


EINVAL

The value specified by mp does not refer to an initialized mutex object.

Mutex Lock Code Examples

Example 4–1 shows some code fragments with mutex locking.


Example 4–1 Mutex Lock Example

#include <pthread.h>

pthread_mutex_t count_mutex;
long long count;

void
increment_count()
{
	    pthread_mutex_lock(&count_mutex);
    count = count + 1;
	    pthread_mutex_unlock(&count_mutex);
}

long long
get_count()
{
    long long c;
    
    pthread_mutex_lock(&count_mutex);
	    c = count;
    pthread_mutex_unlock(&count_mutex);
	    return (c);
}

The two functions in Example 4–1 use the mutex lock for different purposes. The increment_count() function uses the mutex lock simply to ensure an atomic update of the shared variable. The get_count() function uses the mutex lock to guarantee that the 64-bit quantity count is read atomically. On a 32-bit architecture, a long long is really two 32-bit quantities.

Reading an integer value is an atomic operation because integer is the common word size on most machines.

Using Locking Hierarchies

You will occasionally want to access two resources at once. Perhaps you are using one of the resources, and then discover that the other resource is needed as well. There could be a problem if two threads attempt to claim both resources but lock the associated mutexes in different orders. For example, if the two threads lock mutexes 1 and 2 respectively, then a deadlock occurs when each attempts to lock the other mutex. Example 4–2 shows possible deadlock scenarios.


Example 4–2 Deadlock

Thread 1 

Thread 2 

 

pthread_mutex_lock(&m1);

 

 

 

 

/* use resource 1 */ 

 

pthread_mutex_lock(&m2);

 

 

/* use resources1 and 2 */ 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

pthread_mutex_lock(&m2);

 

 

/* use resource 2 */ 

 

pthread_mutex_lock(&m1);

 

/* use resources 1 and 2 */ 

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);

 


The best way to avoid this problem is to make sure that whenever threads lock multiple mutexes, they do so in the same order. When locks are always taken in a prescribed order, deadlock should not occur. This technique is known as lock hierarchies: order the mutexes by logically assigning numbers to them.

Also, honor the restriction that you cannot take a mutex that is assigned n when you are holding any mutex assigned a number greater than n.

However, this technique cannot always be used—sometimes you must take the mutexes in an order other than prescribed. To prevent deadlock in such a situation, use pthread_mutex_trylock(). One thread must release its mutexes when it discovers that deadlock would otherwise be inevitable.


Example 4–3 Conditional Locking

Thread 1 

Thread 2 

pthread_mutex_lock(&m1); pthread_mutex_lock(&m2);

 

 

 

 

/* no processing */ 

 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

for (; ;)

{ pthread_mutex_lock(&m2);

 

 

if(pthread_mutex_trylock(&m1)==0)

/* got it! */  

break;

/* didn't get it */ 

pthread_mutex_unlock(&m2);

}

/* get locks; no processing */ 

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


In Example 4–3, thread 1 locks mutexes in the prescribed order, but thread 2 takes them out of order. To make certain that there is no deadlock, thread 2 has to take mutex 1 very carefully; if it were to block waiting for the mutex to be released, it is likely to have just entered into a deadlock with thread 1.

To ensure this does not happen, thread 2 calls pthread_mutex_trylock(), which takes the mutex if it is available. If it is not, thread 2 returns immediately, reporting failure. At this point, thread 2 must release mutex 2, so that thread 1 can lock it, and then release both mutex 1 and mutex 2.

Nested Locking With a Singly Linked List

Example 4–4 and Example 4–5 show how to take three locks at once, but prevent deadlock by taking the locks in a prescribed order.


Example 4–4 Singly Linked List Structure

typedef struct node1 {
    int value;
    struct node1 *link;
    pthread_mutex_t lock;
} node1_t;

node1_t ListHead;

This example uses a singly linked list structure with each node containing a mutex. To remove a node from the list, first search the list starting at ListHead (which itself is never removed) until the desired node is found.

To protect this search from the effects of concurrent deletions, lock each node before any of its contents are accessed. Because all searches start at ListHead, there is never a deadlock because the locks are always taken in list order.

When the desired node is found, lock both the node and its predecessor since the change involves both nodes. Because the predecessor's lock is always taken first, you are again protected from deadlock. Example 4–5 shows the C code to remove an item from a singly linked list.


Example 4–5 Singly Linked List With Nested Locking

node1_t *delete(int value)
{
    node1_t *prev, *current;

    prev = &ListHead;
    pthread_mutex_lock(&prev->lock);
    while ((current = prev->link) != NULL) {
        pthread_mutex_lock(&current->lock);
        if (current->value == value) {
            prev->link = current->link;
            pthread_mutex_unlock(&current->lock);
            pthread_mutex_unlock(&prev->lock);
            current->link = NULL;
            return(current);
        }
        pthread_mutex_unlock(&prev->lock);
        prev = current;
    }
    pthread_mutex_unlock(&prev->lock);
    return(NULL);
}

Nested Locking With a Circular Linked List

Example 4–6 modifies the previous list structure by converting it into a circular list. There is no longer a distinguished head node; now a thread might be associated with a particular node and might perform operations on that node and its neighbor. Note that lock hierarchies do not work easily here because the obvious hierarchy (following the links) is circular.


Example 4–6 Circular Linked List Structure

typedef struct node2 {
    int value;
    struct node2 *link;
    pthread_mutex_t lock;
} node2_t;

Here is the C code that acquires the locks on two nodes and performs an operation involving both of them.


Example 4–7 Circular Linked List With Nested Locking

void Hit Neighbor(node2_t *me) {
    while (1) {
        pthread_mutex_lock(&me->lock);
        if (pthread_mutex_lock(&me->link->lock)!= 0) {
            /* failed to get lock */             
            pthread_mutex_unlock(&me->lock);              
            continue;         
        }         
        break;     
    }     
    me->link->value += me->value;     
    me->value /=2;     
    pthread_mutex_unlock(&me->link->lock);     
    pthread_mutex_unlock(&me->lock);
}
 


Condition Variable Attributes

Use condition variables to atomically block threads until a particular condition is true. Always use condition variables together with a mutex lock.

With a condition variable, a thread can atomically block until a condition is satisfied. The condition is tested under the protection of a mutual exclusion lock (mutex).

When the condition is false, a thread usually blocks on a condition variable and atomically releases the mutex waiting for the condition to change. When another thread changes the condition, it can signal the associated condition variable to cause one or more waiting threads to wake up, acquire the mutex again, and reevaluate the condition.

Condition variables can be used to synchronize threads among processes when they are allocated in memory that can be written to and is shared by the cooperating processes.

The scheduling policy determines how blocking threads are awakened. For the default SCHED_OTHER, threads are awakened in priority order.

The attributes for condition variables must be set and initialized before the condition variables can be used. The functions that manipulate condition variable attributes are listed in Table 4–4.

Table 4–4 Condition Variable Attributes

Operation 

Destination Discussion 

 

Initialize a condition variable attribute 

pthread_condattr_init(3THR)

 

Remove a condition variable attribute 

pthread_condattr_destroy(3THR)

 

Set the scope of a condition variable  

pthread_condattr_setpshared(3THR)

 

Get the scope of a condition variable 

pthread_condattr_getpshared(3THR)

 

The differences between Solaris and POSIX threads, when defining the scope of a condition variable, are shown in Table 4–5.

Table 4–5 Condition Variable Scope Comparison

Solaris 

POSIX 

Definition 

USYNC_PROCESS

PTHREAD_PROCESS_SHARED

Use to synchronize threads in this and other processes 

USYNC_THREAD

PTHREAD_PROCESS_PRIVATE

Use to synchronize threads in this process only 

Initialize a Condition Variable Attribute

pthread_condattr_init(3THR)

Use pthread_condattr_init(3THR) to initialize attributes associated with this object to their default values. Storage for each attribute object is allocated by the threads system during execution. The default value of the pshared attribute when this function is called is PTHREAD_PROCESS_PRIVATE, which means that the initialized condition variable can be used within a process.

Prototype:
int	pthread_condattr_init(pthread_condattr_t *cattr);
#include <pthread.h>
pthread_condattr_t  cattr;
int ret;

/* initialize an attribute to default value */
ret = pthread_condattr_init(&cattr); 

cattr is an opaque data type that contains a system-allocated attribute object. The possible values of cattr's scope are PTHREAD_PROCESS_PRIVATE (the default) and PTHREAD_PROCESS_SHARED.

Before a condition variable attribute can be reused, it must first be reinitialized by pthread_condattr_destroy(3THR). The pthread_condattr_init() call returns a pointer to an opaque object. If the object is not destroyed, a memory leak will result.

Return Values

pthread_condattr_init() 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.


ENOMEM

There is not enough memory to initialize the thread attributes object.


EINVAL

The value specified by cattr is invalid.

Remove a Condition Variable Attribute

pthread_condattr_destroy(3THR)

Use pthread_condattr_destroy(3THR) to remove storage and render the attribute object invalid.

Prototype:
int	pthread_condattr_destroy(pthread_condattr_t *cattr);
#include <pthread.h>
pthread_condattr_t cattr;
int ret;

/* destroy an attribute */
ret
 = pthread_condattr_destroy(&cattr); 

Return Values

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


EINVAL

The value specified by cattr is invalid.

Set the Scope of a Condition Variable

pthread_condattr_setpshared(3THR)

pthread_condattr_setpshared(3THR) sets the scope of a condition variable to either process private (intraprocess) or system wide (interprocess). If the condition variable is created with the pshared attribute set to the PTHREAD_PROCESS_SHARED state, and it exists in shared memory, it can be shared among threads from more than one process. This is equivalent to the USYNC_PROCESS flag in mutex_init() in the original Solaris threads.

If the mutex pshared attribute is set to PTHREAD_PROCESS_PRIVATE (default value), only those threads created by the same process can operate on the mutex. Using PTHREAD_PROCESS_PRIVATE results in the same behavior as with the USYNC_THREAD flag in the original Solaris threads cond_init() call, which is that of a local condition variable. PTHREAD_PROCESS_SHARED is equivalent to a global condition variable.

Prototype:
int	pthread_condattr_setpshared(pthread_condattr_t *cattr,
    int pshared);
#include <pthread.h>

pthread_condattr_t cattr;
int ret;

/* all processes */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);

/* within a process */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE);

Return Values

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


EINVAL

The value of cattr is invalid, or the pshared value is invalid.

Get the Scope of a Condition Variable

pthread_condattr_getpshared(3THR)

pthread_condattr_getpshared(3THR) gets the current value of pshared for the attribute object cattr. The value is either PTHREAD_PROCESS_SHARED or PTHREAD_PROCESS_PRIVATE.

Prototype:
int	pthread_condattr_getpshared(const pthread_condattr_t *cattr,
    int *pshared);
#include <pthread.h>

pthread_condattr_t cattr;
int pshared;
int ret;

/* get pshared value of condition variable */
ret = pthread_condattr_getpshared(&cattr, &pshared); 

Return Values

pthread_condattr_getpshared() 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

The value of cattr is invalid.

Using Condition Variables

This section explains using condition variables. Table 4–6 lists the functions that are available.

Table 4–6 Condition Variables Functions

Operation 

Destination Discussion 

Initialize a condition variable 

pthread_cond_init(3THR)

Block on a condition variable 

pthread_cond_wait(3THR)

Unblock a specific thread 

pthread_cond_signal(3THR)

Block until a specified time 

pthread_cond_timedwait(3THR)

Block for a specified interval 

pthread_cond_reltimedwait_np(3THR)

Unblock all threads 

pthread_cond_broadcast(3THR)

Destroy condition variable state 

pthread_cond_destroy(3THR)

Initialize a Condition Variable

pthread_cond_init(3THR)

Use pthread_cond_init(3THR) to initialize the condition variable pointed at by cv to its default value (cattr is NULL), or to specify condition variable attributes that are already set with pthread_condattr_init(). The effect of cattr being NULL is the same as passing the address of a default condition variable attribute object, but without the memory overhead. (For Solaris threads, see cond_init(3THR).)

Prototype:
int	pthread_cond_init(pthread_cond_t *cv,
    const pthread_condattr_t *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); 

Statically defined condition variables can be initialized directly to have default attributes with the macro PTHREAD_COND_INITIALIZER. This 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 destroyed, the application must be sure the condition variable is not in use.

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

The value specified by cattr is invalid.


EBUSY

The condition variable is being used.


EAGAIN

The necessary resources are not available.


ENOMEM

There is not enough memory to initialize the condition variable.

Block on a Condition Variable

pthread_cond_wait(3THR)

Use pthread_cond_wait(3THR) 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. (For Solaris threads, see cond_wait(3THR).)

Prototype:
int	pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *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 associated with the condition variable cannot be inferred by the return of pthread_cond_wait(), and any such condition 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. It atomically releases the associated mutex lock before blocking, and atomically acquires it 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 it changes the condition value. This causes one or all of the threads waiting on the condition to unblock and to try to acquire the mutex lock again.

Because the condition can change before an awakened thread reacquires the mutes and returns from pthread_cond_wait(), and because a waiting thread can be awakened spuriously, the condition that caused the wait must be retested before continuing execution from the point of the pthread_cond_wait(). 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();

No specific order of acquisition is guaranteed when more than one thread blocks on the condition variable.


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.


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

The value specified by cv or mp is invalid.

Unblock One Thread

pthread_cond_signal(3THR)

Use pthread_cond_signal(3THR) to unblock one thread that is blocked on the condition variable pointed to by cv. (For Solaris threads, see cond_signal(3THR).)

Prototype:
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); 

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

The scheduling policy determines the order in which blocked threads are awakened. For SCHED_OTHER, threads are awakened in priority order.

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

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

cv points to an illegal address.

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


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);
}

Block Until a Specified Time

pthread_cond_timedwait(3THR)

Prototype:
int	pthread_cond_timedwait(pthread_cond_t *cv,
    pthread_mutex_t *mp, const struct timespec *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); 

Use pthread_cond_timedwait(3THR) 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() always returns with the mutex locked and owned by the calling thread, even when it is returning an error. (For Solaris threads, see cond_timedwait(3THR).)

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.


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

cv or abstime points to an illegal address.


ETIMEDOUT

The time specified by abstime has passed.

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.


Example 4–9 Timed Condition Wait

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

Block For a Specified Interval

pthread_cond_reltimedwait_np(3THR)

Prototype:
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); 

Use pthread_cond_reltimedwait_np(3THR) as you would use pthread_cond_timedwait(), except that 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() always returns with the mutex locked and owned by the calling thread, even when it is returning an error. (For Solaris threads, see cond_reltimedwait(3THR).) 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.


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

cv or reltime points to an illegal address.


ETIMEDOUT

The time interval specified by reltime has passed.

Unblock All Threads

pthread_cond_broadcast(3THR)

Prototype:
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); 

Use pthread_cond_broadcast(3THR) to unblock all threads that are blocked on the condition variable pointed to by cv, specified by pthread_cond_wait(). When no threads are blocked on the condition variable, pthread_cond_broadcast() has no effect. (For Solaris threads, see cond_broadcast(3THR).)

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

cv points to an illegal address.

Condition Variable Broadcast Example

Since pthread_cond_broadcast() causes all threads blocked on the condition to contend again for the mutex lock, use it 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() it does not matter whether resources is updated first or if pthread_cond_broadcast() is called first inside the mutex lock.

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

Destroy Condition Variable State

pthread_cond_destroy(3THR)

Use pthread_cond_destroy(3THR) to destroy any state associated with the condition variable pointed to by cv. (For Solaris threads, see cond_destroy(3THR).)

Prototype:
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.

Return Values

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


EINVAL

The value specified by cv is invalid.

The Lost Wake-Up Problem

Calling 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:

The Producer/Consumer Problem

This 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 must wait until the buffer has space before it can put something in, and a consumer must wait until something is in the buffer before it can take something out.

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

Example 4–11 has two such queues, one (less) for producers waiting for a slot in the buffer, and the other (more) for consumers waiting 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 The Producer/Consumer Problem and 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 and then makes certain that space is available for the item being produced. If not, it calls pthread_cond_wait(), which causes it to join the queue of threads waiting for the condition less, representing there is room in the buffer, to be signaled.

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(), it must acquire the lock on the mutex again.

This ensures that it again has mutually exclusive access to the buffer data structure. The thread then must check that there really is room available in the buffer; if so, it puts its item 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 there are no waiting consumers, 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/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 (that is, nonzero), but causes the program to abort if the argument evaluates to false (zero). Such assertions are especially useful in multithreaded programs—they immediately point out runtime problems if they fail, and they have the additional effect of being useful comments.

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

Both the assertion and the comments are examples of invariants. These are logical statements that should not be falsified by the execution of the program, except 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 it.)

Using invariants is an extremely useful technique. Even if they 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 is in the part of the code where the comment appears. If you move this comment to just after the mutex_unlock(), this does not necessarily remain true. If you move this comment to just after the assert(), this is still true.

The point is that this invariant expresses a property that is true at all times, except 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), it 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. Its flow is symmetric with that of the producer.


Example 4–13 The Producer/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);
}

Semaphores

Semaphores are a programming construct designed by E. W. Dijkstra in the late 1960s. Dijkstra's model was the operation of railroads: consider a stretch of railroad in which there is a single track over which only one train at a time is allowed.

Guarding this track is a semaphore. A train must wait before entering the single track until the semaphore is in a state that permits travel. When the train enters the track, the semaphore changes state to prevent other trains from entering the track. A train that is leaving this section of track must again change the state of the semaphore to allow another train to enter.

In the computer version, a semaphore appears to be a simple integer. A thread waits for permission to proceed and then signals that it has proceeded by performing a P operation on the semaphore.

The semantics of the operation are such that the thread must wait until the semaphore's value is positive, then change the semaphore's value by subtracting one from it. When it is finished, the thread performs a V operation, which changes the semaphore's value by adding one to it. It is crucial that these operations take place atomically—they cannot be subdivided into pieces between which other actions on the semaphore can take place. In the P operation, the semaphore's value must be positive just before it is decremented (resulting in a value that is guaranteed to be nonnegative and one less than what it was before it was decremented).

In both P and V operations, the arithmetic must take place without interference. If two V operations are performed simultaneously on the same semaphore, the net effect should be that the semaphore's new value is two greater than it was.

The mnemonic significance of P and V is unclear to most of the world, as Dijkstra is Dutch. However, in the interest of true scholarship: P stands for prolagen, a made-up word derived from proberen te verlagen, which means try to decrease. V stands for verhogen, which means increase. This is discussed in one of Dijkstra's technical notes, EWD 74.

sem_wait(3RT) and sem_post(3RT) correspond to Dijkstra's P and V operations. sem_trywait(3RT) is a conditional form of the P operation: if the calling thread cannot decrement the value of the semaphore without waiting, the call returns immediately with a nonzero value.

There are two basic sorts of semaphores: binary semaphores, which never take on values other than zero or one, and counting semaphores, which can take on arbitrary nonnegative values. A binary semaphore is logically just like a mutex.

However, although it is not enforced, mutexes should be unlocked only by the thread holding the lock. There is no notion of “the thread holding the semaphore,” so any thread can perform a V (or sem_post(3RT)) operation.

Counting semaphores are about as powerful as conditional variables (used in conjunction with mutexes). In many cases, the code might be simpler when it is implemented with counting semaphores rather than with condition variables (as shown in the next few examples).

However, when a mutex is used with condition variables, there is an implied bracketing—it is clear which part of the program is being protected. This is not necessarily the case for a semaphore, which might be called the go to of concurrent programming—it is powerful but too easy to use in an unstructured, indeterminate way.

Counting Semaphores

Conceptually, a semaphore is a nonnegative integer count. Semaphores are typically used to coordinate access to resources, with the semaphore count initialized to the number of free resources. Threads then atomically increment the count when resources are added and atomically decrement the count when resources are removed.

When the semaphore count becomes zero, indicating that no more resources are present, threads trying to decrement the semaphore block wait until the count becomes greater than zero.

Table 4–7 Routines for Semaphores

Operation 

Destination Discussion 

Initialize a semaphore 

sem_init(3RT)

Increment a semaphore 

sem_post(3RT)

Block on a semaphore count 

sem_wait(3RT)

Decrement a semaphore count 

sem_trywait(3RT)

Destroy the semaphore state 

sem_destroy(3RT)

Because semaphores need not be acquired and released by the same thread, they can be used for asynchronous event notification (such as in signal handlers). And, because semaphores contain state, they can be used asynchronously without acquiring a mutex lock as is required by condition variables. However, semaphores are not as efficient as mutex locks.

By default, there is no defined order of unblocking if multiple threads are waiting for a semaphore.

Semaphores must be initialized before use, but they do not have attributes.

Initialize a Semaphore

sem_init(3RT)

Prototype:
int	sem_init(sem_t *sem, int pshared, unsigned int value);
#include <semaphore.h>

sem_t sem;
int pshared;
int ret;
int value;

/* initialize a private semaphore */
pshared = 0;
value = 1;
ret = sem_init(&sem, pshared, value); 

Use sema_init(3THR) to initialize the semaphore variable pointed to by sem to value amount. If the value of pshared is zero, then the semaphore cannot be shared between processes. If the value of pshared is nonzero, then the semaphore can be shared between processes. (For Solaris threads, see sema_init(3THR).)

Multiple threads must not initialize the same semaphore.

A semaphore must not be reinitialized while other threads might be using it.

Return Values

sem_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

The value argument exceeds SEM_VALUE_MAX.


ENOSPC

A resource required to initialize the semaphore has been exhausted. The limit on semaphores SEM_NSEMS_MAX has been reached.


EPERM

The process lacks the appropriate privileges to initialize the semaphore.

Initializing Semaphores With Intraprocess Scope

When pshared is 0, the semaphore can be used by all the threads in this process only.

#include <semaphore.h>

sem_t sem;
int ret;
int count = 4;

/* to be used within this process only */
ret = sem_init(&sem, 0, count); 

Initializing Semaphores With Interprocess Scope

When pshared is nonzero, the semaphore can be shared by other processes.

#include <semaphore.h>

sem_t sem;
int ret;
int count = 4;

/* to be shared among processes */
ret = sem_init(&sem, 1, count);

Named Semaphores

The functions sem_open(3RT), sem_getvalue(3RT), sem_close(3RT), and sem_unlink(3RT) are available to open, retrieve, close, and remove named semaphores. Using sem_open(), you can create a semaphore that has a name defined in the file system name space.

Named semaphores are like process shared semaphores, except that they are referenced with a pathname rather than a pshared value.

For more information about named semaphores, see sem_open(3RT), sem_getvalue(3RT), sem_close(3RT), and sem_unlink(3RT).

Increment a Semaphore

sem_post(3RT)

Prototype:
int	sem_post(sem_t *sem);
#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_post(&sem); /* semaphore is posted */

Use sema_post(3THR) to atomically increment the semaphore pointed to by sem. When any threads are blocked on the semaphore, one of them is unblocked. (For Solaris threads, see sema_post(3THR).)

Return Values

sem_post() 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

sem points to an illegal address.

Block on a Semaphore Count

sem_wait(3RT)

Prototype:
int	sem_wait(sem_t *sem);
#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_wait(&sem); /* wait for semaphore */

Use sema_wait(3THR) to block the calling thread until the count in the semaphore pointed to by sem becomes greater than zero, then atomically decrement it.

Return Values

sem_wait() 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

sem points to an illegal address.


EINTR

A signal interrupted this function.

Decrement a Semaphore Count

sem_trywait(3RT)

Prototype:
int	sem_trywait(sem_t *sem);
#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_trywait(&sem); /* try to wait for semaphore*/

Use sem_trywait(3RT) to try to atomically decrement the count in the semaphore pointed to by sem when the count is greater than zero. This function is a nonblocking version of sem_wait(); that is it returns immediately if unsuccessful.

Return Values

sem_trywait() 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

sem points to an illegal address.


EINTR

A signal interrupted this function.


EAGAIN

The semaphore was already locked, so it cannot be immediately locked by the sem_trywait() operation.

Destroy the Semaphore State

sem_destroy(3RT)

Prototype:
int	sem_destroy(sem_t *sem);
#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_destroy(&sem); /* the semaphore is destroyed */

Use sem_destroy(3RT) to destroy any state associated with the semaphore pointed to by sem. The space for storing the semaphore is not freed. (For Solaris threads, see sem_destroy(3THR).)

Return Values

sem_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

sem points to an illegal address.

The Producer/Consumer Problem, Using Semaphores

The data structure in Example 4–14 is similar to that used for the condition variables example (see Example 4–11). Two semaphores represent the number of full and empty buffers and ensure that producers wait until there are empty buffers and that consumers wait until there are full buffers.


Example 4–14 The Producer/Consumer Problem With Semaphores

typedef struct {
    char buf[BSIZE];
    sem_t occupied;
    sem_t empty;
    int nextin;
    int nextout;
    sem_t pmut;
    sem_t cmut;
} buffer_t;

buffer_t buffer;

sem_init(&buffer.occupied, 0, 0);
sem_init(&buffer.empty,0, BSIZE);
sem_init(&buffer.pmut, 0, 1);
sem_init(&buffer.cmut, 0, 1);
buffer.nextin = buffer.nextout = 0;

Another pair of (binary) semaphores plays the same role as mutexes, controlling access to the buffer when there are multiple producers and multiple empty buffer slots, and when there are multiple consumers and multiple full buffer slots. Mutexes would work better here, but would not provide as good an example of semaphore use.


Example 4–15 The Producer/Consumer Problem—the Producer

void producer(buffer_t *b, char item) {
    sem_wait(&b->empty);
    sem_wait(&b->pmut);

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

    sem_post(&b->pmut);
    sem_post(&b->occupied);
}


Example 4–16 The Producer/Consumer Problem—the Consumer

char consumer(buffer_t *b) {
    char item;

    sem_wait(&b->occupied);
   
    sem_wait(&b->cmut);

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

    sem_post(&b->cmut);

    sem_post(&b->empty);

    return(item);
}

Read-Write Lock Attributes

Read-write locks permit concurrent reads and exclusive writes to a protected shared resource. The read-write lock is a single entity that can be locked in read or write mode. To modify a resource, a thread must first acquire the exclusive write lock. An exclusive write lock is not permitted until all read locks have been released.

Database access can be synchronized with a read-write lock. Read-write locks support concurrent reads of database records because the read operation does not change the record's information. When the database is to be updated, the write operation must acquire an exclusive write lock.

To change the default read-write lock attributes, you can declare and initialize an attribute object. Often, the read-write lock attributes are set up in one place at the beginning of the application so they can be located quickly and modified easily. The following table lists the functions discussed in this section that manipulate read-write lock attributes.

See Similar Synchronization Functions—Read-Write Locks for the Solaris threads implementation of read-write locks.

Table 4–8 Routines for Read-Write Lock Attributes

Operation 

Destination Discussion 

Initialize a read-write lock attribute 

pthread_rwlockattr_init(3THR)

Destroy a read-write lock attribute 

pthread_rwlockattr_destroy(3THR)

Set a read-write lock attribute 

pthread_rwlockattr_setpshared(3THR)

Get a read-write lock attribute 

pthread_rwlockattr_getpshared(3THR)

Initialize a Read-Write Lock Attribute

pthread_rwlockattr_init(3THR)

#include <pthread.h>

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

pthread_rwlockattr_init(3THR) initializes a read-write lock attributes object attr with the default value for all of the attributes defined by the implementation.

Results are undefined if pthread_rwlockattr_init is called specifying an already initialized read-write lock attributes object. After a read-write lock attributes object has been used to initialize one or more read-write locks, any function affecting the attributes object (including destruction) does not affect any previously initialized read-write locks.

Return Values

If successful, pthread_rwlockattr_init() returns zero. Otherwise, an error number is returned to indicate the error.


ENOMEM

Insufficient memory exists to initialize the rwlock attributes object.

Destroy a Read-Write Lock Attribute

pthread_rwlockattr_destroy(3THR)

#include <pthread.h>

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

pthread_rwlockattr_destroy(3THR) destroys a read-write lock attributes object. The effect of subsequent use of the object is undefined until the object is re-initialized by another call to pthread_rwlockattr_init() An implementation can cause pthread_rwlockattr_destroy() to set the object referenced by attr to an invalid value.

Return Values

If successful, pthread_rwlockattr_destroy() returns zero. Otherwise, an error number is returned to indicate the error.


EINVAL

The value specified by attr is invalid.

Set a Read-Write Lock Attribute

pthread_rwlockattr_setpshared(3THR)

#include <pthread.h>

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t  *attr, 
											int  pshared);

pthread_rwlockattr_setpshared(3THR) sets the process-shared read-write lock attribute.


PTHREAD_PROCESS_SHARED

Permits a read-write lock to be operated on by any thread that has access to the memory where the read-write lock is allocated, even if the read-write lock is allocated in memory that is shared by multiple processes.


PTHREAD_PROCESS_PRIVATE

The read-write lock will only be operated upon by threads created within the same process as the thread that initialized the read-write lock; if threads of differing processes attempt to operate on such a read-write lock, the behavior is undefined. The default value of the process-shared attribute is PTHREAD_PROCESS_PRIVATE.

Return Value

If successful, pthread_rwlockattr_setpshared() returns zero. Otherwise, an error number is returned to indicate the error.


EINVAL

The value specified by attr or pshared is invalid.

Get a Read-Write Lock Attribute

pthread_rwlockattr_getpshared(3THR)

#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t  *attr, 
											int *pshared);

pthread_rwlockattr_getpshared(3THR) gets the process-shared read-write lock attribute.

pthread_rwlockattr_getpshared() obtains the value of the process-shared attribute from the initialized attributes object referenced by attr.

Return Value

If successful, pthread_rwlockattr_getpshared() returns zero. Otherwise, an error number is returned to indicate the error.


EINVAL

The value specified by attr or pshared is invalid.

Using Read-Write Locks

After the attributes for a read-write lock are configured, you initialize the read-write lock itself. The following functions are used to initialize or destroy, lock or unlock, or try to lock a read-write lock. The following table lists the functions discussed in this section that manipulate read-write locks.

Table 4–9 Routines that Manipulate Read-Write Locks

Operation 

Destination Discussion 

Initialize a read-write lock 

pthread_rwlock_init(3THR)

Read lock on read-write lock 

pthread_rwlock_rdlock(3THR)

Read lock with a nonblocking read-write lock 

pthread_rwlock_tryrdlock(3THR)

Write lock on read-write lock 

pthread_rwlock_wrlock(3THR)

Write lock with a nonblocking read-write lock 

pthread_rwlock_trywrlock(3THR)

Unlock a read-write lock 

pthread_rwlock_unlock(3THR)

Destroy a read-write lock 

pthread_rwlock_destroy(3THR)

Initialize a Read-Write Lock

pthread_rwlock_init(3THR)

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *rwlock, 
								const pthread_rwlockattr_t *attr);

pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;

Use pthread_rwlock_init(3THR) to initialize the read-write lock referenced by rwlock with the attributes referenced by attr. If attr is NULL, the default read-write lock attributes are used; the effect is the same as passing the address of a default read-write lock attributes object. Once initialized, the lock can be used any number of times without being re-initialized. On successful initialization, the state of the read-write lock becomes initialized and unlocked. Results are undefined if pthread_rwlock_init() is called specifying an already initialized read-write lock. Results are undefined if a read-write lock is used without first being initialized. (For Solaris threads, see rwlock_init(3THR).)

In cases where default read-write lock attributes are appropriate, the macro PTHREAD_RWLOCK_INITIALIZER can be used to initialize read-write locks that are statically allocated. The effect is equivalent to dynamic initialization by a call to pthread_rwlock_init() with the parameter attr specified as NULL, except that no error checks are performed.

Return Value

If successful, pthread_rwlock_init() returns zero. Otherwise, an error number is returned to indicate the error.

If pthread_rwlock_init() fails, rwlock is not initialized and the contents of rwlock are undefined.


EINVAL

The value specified by attr or rwlock is invalid.

Read Lock on Read-Write Lock

pthread_rwlock_rdlock(3THR)

#include <pthread.h>

int  pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );

pthread_rwlock_rdlock(3THR) applies a read lock to the read-write lock referenced by rwlock. The calling thread acquires the read lock if a writer does not hold the lock and there are no writers blocked on the lock. It is unspecified whether the calling thread acquires the lock when a writer does not hold the lock and there are writers waiting for the lock. If a writer holds the lock, the calling thread will not acquire the read lock. If the read lock is not acquired, the calling thread blocks (that is, it does not return from the pthread_rwlock_rdlock()) until it can acquire the lock. Results are undefined if the calling thread holds a write lock on rwlock at the time the call is made.

Implementations are allowed to favor writers over readers to avoid writer starvation. (For instance, the Solaris threads implementation favors writers over readers. See rw_rdlock(3THR).)

A thread can hold multiple concurrent read locks on rwlock (that is, successfully call pthread_rwlock_rdlock() n times) If so, the thread must perform matching unlocks (that is, it must call pthread_rwlock_unlock() n times).

Results are undefined if pthread_rwlock_rdlock() is called with an uninitialized read-write lock.

If a signal is delivered to a thread waiting for a read-write lock for reading, on return from the signal handler the thread resumes waiting for the read-write lock for reading as if it was not interrupted.

Return Value

If successful, pthread_rwlock_rdlock() returns zero. Otherwise, an error number is returned to indicate the error.


EINVAL

The value specified by attr or rwlock is invalid.

Read Lock With a Nonblocking Read-Write Lock

pthread_rwlock_tryrdlock(3THR)

#include <pthread.h>

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

pthread_rwlock_tryrdlock(3THR) applies a read lock as in pthread_rwlock_rdlock() with the exception that the function fails if any thread holds a write lock on rwlock or there are writers blocked on rwlock. (For Solaris threads, see rw_tryrdlock(3THR).)

Return Value

pthread_rwlock_tryrdlock() returns zero if the lock for reading on the read-write lock object referenced by rwlock is acquired. Otherwise an error number is returned to indicate the error.


EBUSY

The read-write lock could not be acquired for reading because a writer holds the lock or was blocked on it.

Write Lock on Read-Write Lock

pthread_rwlock_wrlock(3THR)

#include <pthread.h>

int  pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );

pthread_rwlock_wrlock(3THR) applies a write lock to the read-write lock referenced by rwlock. The calling thread acquires the write lock if no other thread (reader or writer) holds the read-write lock rwlock. Otherwise, the thread blocks (that is, does not return from the pthread_rwlock_wrlock() call) until it can acquire the lock. Results are undefined if the calling thread holds the read-write lock (whether a read or write lock) at the time the call is made.

Implementations are allowed to favor writers over readers to avoid writer starvation. (For instance, the Solaris threads implementation favors writers over readers. See rw_wrlock(3THR).)

Results are undefined if pthread_rwlock_wrlock() is called with an uninitialized read-write lock.

If a signal is delivered to a thread waiting for a read-write lock for writing, upon return from the signal handler the thread resumes waiting for the read-write lock for writing as if it was not interrupted.

Return Value

pthread_rwlock_rwlock() returns zero if the lock for writing on the read-write lock object referenced by rwlock is acquired. Otherwise an error number is returned to indicate the error.

Write Lock With a Nonblocking Read-Write Lock

pthread_rwlock_trywrlock(3THR)

#include <pthread.h>

int pthread_rwlock_trywrlock(pthread_rwlock_t  *rwlock);

pthread_rwlock_trywrlock(3THR) applies a write lock like pthread_rwlock_wrlock(), with the exception that the function fails if any thread currently holds rwlock (for reading or writing). (For Solaris threads, see rw_trywrlock(3THR).)

Results are undefined if pthread_rwlock_trywrlock() is called with an uninitialized read-write lock.

If a signal is delivered to a thread waiting for a read-write lock for writing, on return from the signal handler the thread resumes waiting for the read-write lock for writing as if it was not interrupted.

Return Value

If successful, pthread_rwlock_trywrlock() returns zero if the lock for writing on the read-write lock object referenced by rwlock is acquired. Otherwise, an error number is returned to indicate the error.


EBUSY

The read-write lock could not be acquired for writing because it is already locked for reading or writing.

Unlock a Read-Write Lock

pthread_rwlock_unlock(3THR)

#include <pthread.h>

pthread_rwlock_unlock(3THR) releases a lock held on the read-write lock object referenced by rwlock. Results are undefined if the read-write lock rwlock is not held by the calling thread. (For Solaris threads, see rw_unlock(3THR).)

If pthread_rwlock_unlock() is called to release a read lock from the read-write lock object and there are other read locks currently held on this read-write lock object, the read-write lock object remains in the read locked state. If pthread_rwlock_unlock() releases the calling thread's last read lock on this read-write lock object, then the calling thread is no longer one of the owners of the object. If pthread_rwlock_unlock() releases the last read lock for this read-write lock object, the read-write lock object will be put in the unlocked state with no owners.

If pthread_rwlock_unlock() is called to release a write lock for this read-write lock object, the read-write lock object will be put in the unlocked state with no owners.

If the call to the pthread_rwlock_unlock() results in the read-write lock object becoming unlocked and there are multiple threads waiting to acquire the read-write lock object for writing, the scheduling policy is used to determine which thread acquires the read-write lock object for writing. If there are multiple threads waiting to acquire the read-write lock object for reading, the scheduling policy is used to determine the order in which the waiting threads acquire the read-write lock object for reading. If there are multiple threads blocked on rwlock for both read locks and write locks, it is unspecified whether the readers acquire the lock first or whether a writer acquires the lock first.

Results are undefined if pthread_rwlock_unlock() is called with an uninitialized read-write lock.

Return Value

If successful, pthread_rwlock_unlock() returns zero. Otherwise, an error number is returned to indicate the error.

Destroy a Read-Write Lock

pthread_rwlock_destroy(3THR)

#include <pthread.h>

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;

pthread_rwlock_destroy(3THR) destroys the read-write lock object referenced by rwlock and releases any resources used by the lock. The effect of subsequent use of the lock is undefined until the lock is re-initialized by another call to pthread_rwlock_init() An implementation can cause pthread_rwlock_destroy() to set the object referenced by rwlock to an invalid value. Results are undefined if pthread_rwlock_destroy() is called when any thread holds rwlock. Attempting to destroy an uninitialized read-write lock results in undefined behavior. A destroyed read-write lock object can be re-initialized using pthread_rwlock_init(); the results of otherwise referencing the read-write lock object after it has been destroyed are undefined. (For Solaris threads, see rwlock_destroy(3THR).)

Return Value

If successful, pthread_rwlock_destroy() returns zero. Otherwise, an error number is returned to indicate the error.


EINVAL

The value specified by attr or rwlock is invalid.

Synchronization Across Process Boundaries

Each of the synchronization primitives can be set up to be used across process boundaries. This is done quite simply by ensuring that the synchronization variable is located in a shared memory segment and by calling the appropriate init() routine, after the primitive has been initialized with its shared attribute set as interprocess.

Producer/Consumer Problem Example

Example 4–17 shows the producer/consumer problem with the producer and consumer in separate processes. The main routine maps zero-filled memory (that it shares with its child process) into its address space.

A child process is created that runs the consumer. The parent runs the producer.

This example also shows the drivers for the producer and consumer. The producer_driver() simply reads characters from stdin and calls producer(). The consumer_driver() gets characters by calling consumer() and writes them to stdout.

The data structure for Example 4–17 is the same as that used for the condition variables example (see Example 4–4). Two semaphores represent the number of full and empty buffers and ensure that producers wait until there are empty buffers and that consumers wait until there are full buffers.


Example 4–17 Synchronization Across Process Boundaries

main() {
    int zfd;
    buffer_t *buffer;
    pthread_mutexattr_t mattr;
    pthread_condattr_t cvattr_less, cvattr_more;

    zfd = open("/dev/zero", O_RDWR);
    buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t),
        PROT_READ|PROT_WRITE, MAP_SHARED, zfd, 0);
    buffer->occupied = buffer->nextin = buffer->nextout = 0;

    pthread_mutex_attr_init(&mattr);
    pthread_mutexattr_setpshared(&mattr,
        PTHREAD_PROCESS_SHARED);

    pthread_mutex_init(&buffer->lock, &mattr);
    pthread_condattr_init(&cvattr_less);
    pthread_condattr_setpshared(&cvattr_less, PTHREAD_PROCESS_SHARED);
    pthread_cond_init(&buffer->less, &cvattr_less);
    pthread_condattr_init(&cvattr_more);
    pthread_condattr_setpshared(&cvattr_more,   
        PTHREAD_PROCESS_SHARED);
    pthread_cond_init(&buffer->more, &cvattr_more);

    if (fork() == 0)
        consumer_driver(buffer);
    else
        producer_driver(buffer);
}

void producer_driver(buffer_t *b) {
    int item;

    while (1) {
        item = getchar();
        if (item == EOF) {
            producer(b, `\0');
            break;
        } else
            producer(b, (char)item);
    }
}

void consumer_driver(buffer_t *b) {
    char item;

    while (1) {
        if ((item = consumer(b)) == '\0')
            break;
        putchar(item);
    }
}

Interprocess Locking Without the Threads Library

Although not generally recommended, you can do interprocess locking without using the threads library. If this is something you want to do, see the instructions in Using LWPs Between Processes.

Comparing Primitives

The most basic synchronization primitive in threads is the mutual exclusion lock. So, it is the most efficient mechanism in both memory use and execution time. The basic use of a mutual exclusion lock is to serialize access to a resource.

The next most efficient primitive in threads is the condition variable. The basic use of a condition variable is to block on a change of state; that is it provides a thread wait facility. Remember that a mutex lock must be acquired before blocking on a condition variable and must be unlocked after returning from pthread_cond_wait(). The mutex lock must also be held across the change of state that occurs before the corresponding call to pthread_cond_signal().

The semaphore uses more memory than the condition variable. It is easier to use in some circumstances because a semaphore variable functions on state rather than on control. Unlike a lock, a semaphore does not have an owner. Any thread can increment a semaphore that has blocked.

The read-write lock permits concurrent reads and exclusive writes to a protected resource. The read-write lock is a single entity that can be locked in read or write mode. To modify a resource, a thread must first acquire the exclusive write lock. An exclusive write lock is not permitted until all read locks have been released.