This chapter describes the synchronization types that are available with threads. The chapter also 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 that are placed in threads-controlled shared memory. The threads can communicate with each other even though the threads in different processes are generally invisible to each other.
Synchronization objects can also be placed in files. The synchronization objects can have lifetimes beyond the life of the creating process.
The available types of synchronization objects are
Mutex locks
Condition variables
Read-Write locks
Semaphores
Situations that can benefit from the use of synchronization include the following:
Synchronization is the only way to ensure consistency of shared data.
Threads in two or more processes can use a single synchronization object jointly. Because reinitializing a synchronization object sets the object to the unlocked state, the synchronization object should be initialized by only one of the cooperating processes.
Synchronization can ensure the safety of mutable data.
A process can map a file and direct a thread in this process get a record's lock. Once the lock is acquired, any thread in any process mapping the file attempting to acquire the lock is blocked until the lock is released.
Accessing a single primitive variable, such as an integer, can use more than one memory cycle for a single memory load. More than one memory cycle is used where the integer is not aligned to the bus data width or is larger than the data width. While integer misalignment cannot happen on the SPARC architecture, portable programs cannot rely on the proper alignment.
On 32-bit architectures, a long
long
is
not atomic. (An atomic operation cannot be divided into
smaller operations.) A long
long
is read and written
as two 32-bit quantities. The types int
, char
, float
, and pointers are atomic on SPARC architecture machines and Intel
Architecture machines.
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 the attributes can be located quickly and modified easily. Table 4–1 lists the functions that manipulate mutex attributes.
Table 4–1 Mutex Attributes Routines
Operation |
Related Function Description |
---|---|
Initialize a mutex attribute object | |
Destroy a mutex attribute object | |
Set the scope of a mutex | |
Get the scope of a mutex | |
Set the mutex type attribute | |
Get the mutex type attribute | |
Set mutex attribute's protocol | |
Get mutex attribute's protocol | |
Set mutex attribute's priority ceiling | |
Get mutex attribute's priority ceiling | |
Set mutex's priority ceiling | |
Get mutex's priority ceiling | |
Set mutex's robust attribute | |
Get mutex's robust attribute |
Use pthread_mutexattr_init(3C) to initialize attributes that are associated with the mutex object to their default values. Storage for each attribute object is allocated by the threads system during execution.
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. See Table 4–2 for information about the attributes in the mattr object.
Before a mutex attribute object can be reinitialized, the object must first be destroyed by a call to pthread_mutexattr_destroy(3C). The pthread_mutexattr_init() call results in the allocation of an opaque object. If the object is not destroyed, a memory leak results.
Table 4–2 Default Attribute Values for mattr
Attribute |
Value |
Result |
---|---|---|
pshared |
PTHREAD_PROCESS_PRIVATE |
The initialized mutex can be used within a process. Only those threads created by the same process can operate on the mutex. |
type |
PTHREAD_MUTEX_DEFAULT |
The Solaris Pthreads implementation maps PTHREAD_MUTEX_DEFAULT to PTHREAD_MUTEX_NORMAL, which does not detect deadlock. |
protocol |
PTHREAD_PRIO_NONE |
Thread priority and scheduling are not affected by the priority of the mutex owned by the thread. |
prioceiling |
– |
The prioceiling value is drawn from the existing priority range for the SCHED_FIFO policy, as returned by the sched_get_priority_min() and sched_get_priority_max() functions. This priority range is determined by the Solaris version on which the mutex is created. |
robustness |
PTHREAD_MUTEX_STALLED_NP |
When the owner of a mutex dies, all future calls to pthread_mutex_lock() for this mutex will be blocked from progress. |
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
Description:Insufficient memory exists to initialize the mutex attribute object.
pthread_mutexattr_destroy(3C) deallocates the storage space used to maintain the attribute object created by pthread_mutexattr_init().
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);
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
Description:The value specified by mattr is invalid.
pthread_mutexattr_setpshared(3C) sets the scope of the mutex variable.
int pthread_mutexattr_setpshared(pthread_mutexattr_t *restrict mattr, int *restrict 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);
The scope of a mutex variable can be either process private (intraprocess) or system wide (interprocess). To share the mutex among threads from more than one process, create the mutex in shared memory with the pshared attribute set to PTHREAD_PROCESS_SHARED .
If the mutex pshared attribute is set to PTHREAD_PROCESS_PRIVATE , only those threads created by the same process can operate on the mutex.
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
Description:The value specified by mattr is invalid.
pthread_mutexattr_getpshared(3C) returns the scope of the mutex variable defined by pthread_mutexattr_setpshared().
int pthread_mutexattr_getpshared(pthread_mutexattr_t *restrict mattr, int *restrict 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. The value is either PTHREAD_PROCESS_SHARED or PTHREAD_PROCESS_PRIVATE.
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
Description:The value specified by mattr is invalid.
pthread_mutexattr_settype(3C) sets the mutex type attribute.
#include <pthread.h> int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);
The default value of the type attribute is PTHREAD_MUTEX_DEFAULT.
The type argument specifies the type of mutex. The following list describes the valid mutex types:
PTHREAD_MUTEX_NORMAL
Description:This type of mutex does not detect deadlock. A thread attempting to relock this mutex without first unlocking the mutex deadlocks. 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
Description:This type of mutex provides error checking. A thread attempting to relock this mutex without first unlocking the mutex returns an error. A thread attempting to unlock a mutex that another thread has locked returns an error. A thread attempting to unlock an unlocked mutex returns an error.
PTHREAD_MUTEX_RECURSIVE
Description:A thread attempting to relock this mutex without first unlocking the mutex succeeds in locking the mutex. The relocking deadlock that 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 that another thread has locked returns an error. A thread attempting to unlock an unlocked mutex returns an error.
PTHREAD_MUTEX_DEFAULT
Description:An implementation is allowed to map this attribute to one of the other mutex types. The Solaris implementation maps this attribute to PTHREAD_PROCESS_NORMAL.
If successful, the pthread_mutexattr_settype function returns zero. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The value type or attr is invalid.
pthread_mutexattr_gettype(3C) gets the mutex type attribute set by pthread_mutexattr_settype().
#include <pthread.h> int pthread_mutexattr_gettype(pthread_mutexattr_t *restrict attr , int *restrict type);
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
PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
PTHREAD_MUTEX_DEFAULT
For a description of each type, see pthread_mutexattr_settype Syntax.
On successful completion, pthread_mutexattr_gettype() returns 0. Any other return value indicates that an error occurred.
EINVAL
Description:The value specified by type is invalid.
pthread_mutexattr_setprotocol(3C) 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 that is applied to the mutex attribute object.
The value of protocol that is defined in pthread.h can be one of the following values: PTHREAD_PRIO_NONE , PTHREAD_PRIO_INHERIT, or PTHREAD_PRIO_PROTECT .
PTHREAD_PRIO_NONE
A thread's priority and scheduling are not affected by the mutex ownership.
PTHREAD_PRIO_INHERIT
This protocol value affects an owning thread's priority and scheduling. When higher-priority threads block on one or more mutexes owned by thrd1 where those mutexes are initialized with PTHREAD_PRIO_INHERIT, thrd1 runs with the higher of its priority or the highest priority of any thread waiting on any of the mutexes owned by thrd1.
If thrd1 blocks on a mutex owned by another thread, thrd3, the same priority inheritance effect recursively propagates to thrd3.
Use PTHREAD_PRIO_INHERIT to avoid priority inversion. Priority inversion occurs when a low-priority thread holds a lock that a higher-priority thread requires. The higher-priority thread cannot continue until the lower-priority thread releases the lock.
Without priority inheritance, the lower priority thread might not be scheduled to run for a long time, causing the higher priority thread to block equally long. Priority inheritance temporarily raises the priority of the lower priority thread so it will be scheduled to run quickly and release the lock, allowing the higher priority thread to acquire it. The lower-priority thread reverts to its lower priority when it releases the lock.
PTHREAD_PRIO_PROTECT
This protocol value affects the priority and scheduling of a thread, such as thrd2, when the thread owns one or more mutexes that are initialized with PTHREAD_PRIO_PROTECT. thrd2 runs with the higher of its priority or the highest-priority ceiling of all mutexes owned by thrd2. Higher-priority threads blocked on any of the mutexes, owned by thrd2, have no effect on the scheduling of thrd2.
The PTHREAD_PRIO_INHERIT and PTHREAD_PRIO_PROTECT mutex attributes are usable only by privileged processes running in the realtime (RT) scheduling class SCHED_FIFO or SCHED_RR.
A thread can simultaneously own several mutexes initialized with a mix of PTHREAD_PRIO_INHERIT and PTHREAD_PRIO_PROTECT. In this case, the thread executes at the highest priority obtained by either of these protocols.
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() might fail and return the corresponding value.
EINVAL
Description:The value specified by attr or protocol is not valid.
EPERM
Description:The caller does not have the privilege to perform the operation.
pthread_mutexattr_getprotocol(3C) gets the protocol attribute of a mutex attribute object.
#include <pthread.h> int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict attr, int *restrict protocol);
attr points to a mutex attribute object created by an earlier call to pthread_mutexattr_init().
protocol contains one of the following protocol attributes: PTHREAD_PRIO_NONE, PTHREAD_PRIO_INHERIT, or PTHREAD_PRIO_PROTECT which are defined by the header <pthread.h>.
On successful completion, pthread_mutexattr_getprotocol() returns 0. Any other return value indicates that an error occurred.
If either of the following conditions occurs, pthread_mutexattr_getprotocol() might fail and return the corresponding value.
EINVAL
Description:The value specified by attr is NULL, or the value specified by attr or protocol is invalid.
EPERM
Description:The caller does not have the privilege to perform the operation.
pthread_mutexattr_setprioceiling(3C) sets the priority ceiling attribute of a mutex attribute object.
#include <pthread.h> int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, int prioceiling);
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 falls within the maximum range of priorities defined by SCHED_FIFO. To avoid priority inversion, set prioceiling to a priority higher than or equal to the highest priority of all threads that might lock the particular mutex.
On successful completion, pthread_mutexattr_setprioceiling() returns 0. Any other return value indicates that an error occurred.
If either of the following conditions occurs, pthread_mutexattr_setprioceiling() might fail and return the corresponding value.
EINVAL
Description:The value specified by attr is NULL or invalid or prioceiling is invalid.
EPERM
Description:The caller does not have the privilege to perform the operation.
pthread_mutexattr_getprioceiling(3C) gets the priority ceiling attribute of a mutex attribute object.
#include <pthread.h> int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *restrict attr, int *restrict prioceiling);
attr designates the attribute object created by an earlier call to pthread_mutexattr_init().
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 falls within the maximum range of priorities defined by SCHED_FIFO. To avoid priority inversion, set prioceiling to a priority higher than or equal to the highest priority of all threads that might lock the particular mutex.
On successful completion, pthread_mutexattr_getprioceiling() returns 0. Any other return value indicates that an error occurred.
If either of the following conditions occurs, pthread_mutexattr_getprioceiling() might fail and return the corresponding value.
EINVAL
Description:The value specified by attr is NULL.
EPERM
Description:The caller does not have the privilege to perform the operation.
pthread_mutexattr_setprioceiling(3C) sets the priority ceiling of a mutex.
#include <pthread.h> int pthread_mutex_setprioceiling(pthread_mutex_t *restrict mutex, int prioceiling, int *restrict old_ceiling);
pthread_mutex_setprioceiling() changes the priority ceiling, prioceiling, of a mutex, mutex. pthread_mutex_setprioceiling() locks a mutex if unlocked, or blocks until pthread_mutex_setprioceiling() successfully locks 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.
On successful completion, pthread_mutex_setprioceiling() returns 0. Any other return value indicates that an error occurred.
If any of the following conditions occurs, pthread_mutex_setprioceiling() might fail and return the corresponding value.
EINVAL
Description:The priority requested by prioceiling is out of range.
EINVAL
Description:The mutex was not initialized with its protocol attribute having the value of THREAD_PRIO_PROTECT.
EPERM
Description:The caller does not have the privilege to perform the operation.
pthread_mutexattr_getprioceiling(3C) gets the priority ceiling of a mutex.
#include <pthread.h> int pthread_mutex_getprioceiling(const pthread_mutex_t *restrict mutex, int *restrict prioceiling);
pthread_mutex_getprioceiling() returns the priority ceiling, prioceiling of a mutex.
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.
If any of the following conditions occurs, pthread_mutex_getprioceiling() might fail and return the corresponding value.
EINVAL
Description:The value specified by mutex does not refer to a currently existing mutex.
EPERM
Description:The caller does not have the privilege to perform the operation.
pthread_mutexattr_setrobust_np(3C) sets the robust attribute of a mutex attribute object.
#include <pthread.h> int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, int *robustness);
pthread_mutexattr_setrobust_np() applies only if the symbol _POSIX_THREAD_PRIO_INHERIT is defined.
In the Solaris 10 and prior releases, the PTHREAD_MUTEX_ROBUST_NP attribute can only be applied to mutexes that are also marked with the PTHREAD_PRIO_INHERIT protocol attribute. This restriction is lifted in subsequent Solaris releases.
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 terminates without unlocking the mutex, usually because its process terminated abnormally. The value of robustness that is defined in pthread.h is PTHREAD_MUTEX_ROBUST_NP or PTHREAD_MUTEX_STALLED_NP. The default value is PTHREAD_MUTEX_STALLED_NP .
PTHREAD_MUTEX_STALLED_NP
When the owner of the mutex terminates without unlocking the mutex, all subsequent calls to pthread_mutex_lock() are blocked from progress in an unspecified manner.
PTHREAD_MUTEX_ROBUST_NP
When the owner of the mutex terminates without unlocking the mutex, the mutex is unlocked. The next owner of this mutex acquires the mutex with an error return of EOWNERDEAD.
Your application must always check the return code from pthread_mutex_lock() for a mutex initialized with the PTHREAD_MUTEX_ROBUST_NP attribute.
The new owner of this mutex should make the state protected by the mutex consistent. This state might have been left inconsistent when the previous owner terminated.
If the new owner is able to make the state consistent, call pthread_mutex_consistent_np() for the mutex before unlocking the mutex. This marks the mutex as consistent and subsequent calls to pthread_mutex_lock() and pthread_mutex_unlock() will behave in the normal manner.
If the new owner is not able to make the state consistent, do not call pthread_mutex_consistent_np() for the mutex, but unlock the mutex.
All waiters are woken up and all subsequent calls to pthread_mutex_lock() fail to acquire the mutex. The return code is ENOTRECOVERABLE. The mutex can be made consistent by calling pthread_mutex_destroy() to uninitialize the mutex, and calling pthread_mutex_int() to reinitialize the mutex. However, the state that was protected by the mutex remains inconsistent and some form of application recovery is required.
If the thread that acquires the lock with EOWNERDEAD terminates without unlocking the mutex, the next owner acquires the lock with an EOWNERDEAD return code.
On successful completion, pthread_mutexattr_setrobust_np() returns 0. Any other return value indicates that an error occurred.
pthread_mutexattr_setrobust_np() might fail if the following condition occurs:
EINVAL
Description:The value specified by attr or robustness is invalid.
pthread_mutexattr_getrobust_np(3C) gets the robust attribute of a mutex attribute object.
#include <pthread.h> int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, int *robustness);
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.
On successful completion, pthread_mutexattr_getrobust_np() returns 0. Any other return value indicates that an error occurred.
pthread_mutexattr_getrobust_np() might fail if the following condition occurs:
EINVAL
Description:The value specified by attr or robustness is invalid.
Table 4–3 lists the functions that manipulate mutex locks.
Table 4–3 Routines for Mutual Exclusion Locks
Operation |
Related Function Description |
---|---|
Initialize a mutex | |
Make mutex consistent | |
Lock a mutex | |
Unlock a mutex | |
Lock with a nonblocking mutex | |
Lock a mutex before a specified time | |
Lock a mutex within a specified time interval | |
Destroy a mutex |
The default scheduling policy, SCHED_OTHER, does not specify the order in which threads can acquire a lock. When multiple SCHED_OTHER threads are waiting for a mutex, the order of acquisition is undefined. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, the behavior is to unblock waiting threads in priority order.
Use pthread_mutex_init(3C) to initialize the mutex pointed at by mp to its default value or to specify mutex attributes that have already been set with pthread_mutexattr_init() . The default value for mattr is NULL .
int pthread_mutex_init(pthread_mutex_t *restrict mp, const pthread_mutexattr_t *restrict 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, the mutex is in an unlocked state. The mutex can be in memory that is shared between processes or in memory private to a process.
For a mutex that is being initialized with the PTHREAD_MUTEX_ROBUST_NP attribute, the mutex memory must be cleared to zero before initialization.
The effect of mattr set to NULL is the same as passing the address of a default mutex attribute object, but without the memory overhead.
Use the macro PTHREAD_MUTEX_INITIALIZER to initialize statically defined mutexes to their default attributes.
Do not reinitialize or destroy a mutex lock while other threads are using the mutex. Program failure results 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.
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
Description:The implementation has detected an attempt to reinitialize the object referenced by mp, a previously initialized but not yet destroyed mutex.
EINVAL
Description:The mattr attribute value is invalid. The mutex has not been modified.
EFAULT
Description:The address for the mutex pointed at by mp is invalid.
If the owner of a robust mutex terminates without unlocking the mutex, the mutex is unlocked and marked inconsistent. The next owner acquires the lock with an EOWNERDEAD return code.
pthread_mutex_consistent_np() makes the mutex object, mutex, consistent after the death of its owner.
#include <pthread.h> int pthread_mutex_consistent_np(pthread_mutex_t *mutex);
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().
The critical section protected by the mutex might have been left in an inconsistent state by a failed owner. In this case, make the mutex consistent only if you can 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 is not held, is undefined.
pthread_mutex_consistent_np() returns zero after completing successfully. Any other return value indicates that an error occurred.
pthread_mutex_consistent_np() fails if the following condition occurs:
EINVAL
Description:The current thread does not own the mutex or the mutex is not a PTHREAD_MUTEX_ROBUST_NP mutex having an inconsistent state.
Use pthread_mutex_lock(3C) to lock the mutex pointed to by mutex.
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 */
When pthread_mutex_lock() returns, the mutex is locked. 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.
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 not locked by the thread 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 the thread has already locked, an error is returned. If a thread attempts to unlock a mutex not locked by the thread or a mutex that is unlocked, an error is 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 1. Every time a thread relocks this mutex, the lock count is incremented by 1. Every time the thread unlocks the mutex, the lock count is decremented by 1. When the lock count reaches 0, the mutex becomes available for other threads to acquire. If a thread attempts to unlock a mutex not locked by the thread or a mutex that is unlocked, an error is returned.
The mutex type PTHREAD_MUTEX_DEFAULT is the same as PTHREAD_MUTEX_NORMAL.
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
Description:The mutex could not be acquired because the maximum number of recursive locks for mutex has been exceeded.
EDEADLK
Description:The current thread already owns the mutex.
If the mutex was initialized with the PTHREAD_MUTEX_ROBUST_NProbustness attribute, pthread_mutex_lock() may return one of the following values:
EOWNERDEAD
Description:The last owner of this mutex terminated 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() behave normally.
If the caller is unable to make the state consistent, do not call pthread_mutex_init() for the mutex. Unlock the mutex instead. 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 terminates while holding the mutex, the next owner acquires the lock with EOWNERDEAD.
ENOTRECOVERABLE
Description:The mutex you are trying to acquire was protecting state left irrecoverable by the mutex's previous owner. The mutex has not been acquired. This irrecoverable condition can occur when:
The lock was previously acquired with EOWNERDEAD
The owner was unable to cleanup the state
The owner unlocked the mutex without making the mutex state consistent
ENOMEM
Description:The limit on the number of simultaneously held mutexes has been exceeded.
Use pthread_mutex_unlock(3C) to unlock the mutex pointed to by mutex.
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 threads are blocked on the mutex object when pthread_mutex_unlock() is called and the mutex becomes available, the scheduling policy determines which thread acquires the mutex. For 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.
pthread_mutex_unlock() 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.
EPERM
Description:The current thread does not own the mutex.
Use pthread_mutex_trylock(3C) to attempt to lock the mutex pointed to by mutex, and return immediately if the mutex is already locked.
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.
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
Description:The mutex could not be acquired because the mutex pointed to by mutex was already locked.
EAGAIN
Description: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 . Additionally, if the robustness argument of pthread_mutexattr_setrobust_np() is PTHREAD_MUTEX_ROBUST_NP, the function fails and returns one of the following values:
EOWNERDEAD
Description:See the discussion in pthread_mutex_lock Return Values.
ENOTRECOVERABLE
Description:See the discussion in pthread_mutex_lock Return Values.
ENOMEM
Description:The limit on the number of simultaneously held mutexes has been exceeded.
Use the pthread_mutex_timedlock(3C) function to attempt until a specified time to lock a mutex object.
This function works as the pthread_mutex_lock() function does, except that it does not block indefinitely. If the mutex is already locked, the calling thread is blocked until the mutex becomes available, but only until the timeout is reached. If the timeout occurs before the mutex becomes available, the function returns.
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
#include <pthread.h> #include <time.h> pthread_mutex_t mutex; timestruct_t abs_timeout; int ret; ret = pthread_mutex_timedlock(&mutex, &abs_timeout);
The pthread_mutex_timedlock() function return 0 if it locks the mutex successfully. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The mutex was created with the protocol attribute having the value PTHREAD_PRIO_PROTECT and the calling thread's priority is higher than the mutex's current priority ceiling.
Description:The value specified by mutex does not refer to an initialized mutex object.
Description:The process or thread would have blocked, and the abs_timeout parameter specified a nanoseconds field value less than 0 or greater than or equal to 1000 million.
ETIMEDOUT
Description:The mutex could not be locked before the specified timeout expired.
See the discussion in pthread_mutex_lock Return Values.
Use the pthread_mutex_reltimedlock_np(3C) function to attempt until a specified amount of time elapses to lock a mutex object.
The timeout expires when the time interval specified by rel_timeout passes, as measured by the CLOCK_REALTIME clock, or if the time interval specified by rel_timeout is negative at the time of the call.
int pthread_mutex_reltimedlock_np(pthread_mutex_t *restrict mutex, const struct timespec *restrict rel_timeout);
#include <pthread.h> #include <time.h> pthread_mutex_t mutex; timestruct_t rel_timeout; int ret; ret = pthread_mutex_reltimedlock_np(&mutex, &rel_timeout);
The pthread_mutex_reltimedlock_np() function returns 0 if it locks the mutex successfully. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The mutex was created with the protocol attribute having the value PTHREAD_PRIO_PROTECT and the calling thread's priority is higher than the mutex's current priority ceiling.
Description:The value specified by mutex does not refer to an initialized mutex object.
Description:The process or thread would have blocked, and the abs_timeout parameter specified a nanoseconds field value less than 0 or greater than or equal to 1000 million.
ETIMEDOUT
Description:The mutex could not be locked before the specified timeout expired.
See the discussion in pthread_mutex_lock Return Values.
Use pthread_mutex_destroy(3C) to destroy any state that is associated with the mutex pointed to by mp .
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.
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
Description:The value specified by mp does not refer to an initialized mutex object.
Example 4–1 shows some code fragments with mutex locking.
#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 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.
When you read an integer value, the operation is atomic because an integer is the common word size on most machines.
Occasionally, you might 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. A problem exists 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, a deadlock occurs when each attempts to lock the other mutex. Example 4–2 shows possible deadlock scenarios.
The best way to avoid this problem is to make sure that when threads lock multiple mutexes, the threads do so in the same order. When locks are always taken in a prescribed order, deadlock should not occur. This technique, known as lock hierarchies, orders the mutexes by logically assigning numbers to the mutexes.
Also, honor the restriction that you cannot take a mutex that is assigned n when you are holding any mutex assigned a number that is 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 the thread discovers that deadlock would otherwise be inevitable.
thread1 |
thread2 |
---|---|
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, thread1 locks mutexes in the prescribed order, but thread2 takes the mutexes out of order. To make certain that no deadlock occurs, thread2 has to take mutex1 very carefully. If thread2 blocks while waiting for the mutex to be released, thread2 is likely to have just entered into a deadlock with thread1.
To ensure that thread2 does not enter into a deadlock, thread2 calls pthread_mutex_trylock(), which takes the mutex if available. If the mutex is not available, thread2 returns immediately, reporting failure. At this point, thread2 must release mutex2. Thread1 can now lock mutex2, and then release both mutex1 and mutex2.
Example 4–4 and Example 4–5 show how to take three locks at once. Deadlock is prevented by taking the locks in a prescribed order.
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 that contains a mutex. To remove a node from the list, first search the list starting at ListHead until the desired node is found. ListHead is never removed.
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, a deadlock cannot occur 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.
node1_t *delete(int value) { node1_t *prev, *current; prev = &ListHead; pthread_mutex_lock(&prev->lock); while ((current = prev->link) != NULL) { pthread_mutex_lock(¤t->lock); if (current->value == value) { prev->link = current->link; pthread_mutex_unlock(¤t->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); }
Example 4–6 modifies the previous list structure by converting the list structure into a circular list. Because a distinguished head node no longer exists, a thread can be associated with a particular node and can perform operations on that node and its neighbor Lock hierarchies do not work easily here because the obvious hierarchy, following the links, is circular.
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 that involves both locks.
void Hit Neighbor(node2_t *me) { while (1) { pthread_mutex_lock(&me->lock); if (pthread_mutex_trylock(&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); }
Spin locks are a low-level synchronization mechanism suitable primarily for use on shared memory multiprocessors. When the calling thread requests a spin lock that is already held by another thread, the second thread spins in a loop to test if the lock has become available. When the lock is obtained, it should be held only for a short time, as the spinning wastes processor cycles. Callers should unlock spin locks before calling sleep operations to enable other threads to obtain the lock.
Spin locks can be implemented using mutexes and conditional variables, but the pthread_spin_* functions are a standardized way to practice spin locking. The pthread_spin_* functions require much lower overhead for locks of short duration.
When performing any lock, a trade-off is made between the processor resources consumed while setting up to block the thread and the processor resources consumed by the thread while it is blocked. Spin locks require few resources to set up the blocking of a thread and then do a simple loop, repeating the atomic locking operation until the lock is available. The thread continues to consume processor resources while it is waiting.
Compared to spin locks, mutexes consume a larger amount of processor resources to block the thread. When a mutex lock is not available, the thread changes its scheduling state and adds itself to the queue of waiting threads. When the lock becomes available, these steps must be reversed before the thread obtains the lock. While the thread is blocked, it consumes no processor resources.
Therefore, spin locks and mutexes can be useful for different purposes. Spin locks might have lower overall overhead for very short-term blocking, and mutexes might have lower overall overhead when a thread will be blocked for longer periods of time.
Use the pthread_spin_init(3C) function to allocate resources required to use a spin lock, and initialize the lock to an unlocked state.
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
#include <pthread.h> pthread_spinlock_t lock; int pshared; int ret; /* initialize a spin lock */ ret = pthread_spin_init(&lock, pshared);
The pshared attribute has one of the following values:
PTHREAD_PROCESS_SHARED
Description:Permits a spin lock to be operated on by any thread that has access to the memory where the spin lock is allocated. Operation on the lock is permitted even if the lock is allocated in memory that is shared by multiple processes.
PTHREAD_PROCESS_PRIVATE
Description:Permits a spin lock to be operated upon only by threads created within the same process as the thread that initialized the spin lock. If threads of differing processes attempt to operate on such a spin lock, the behavior is undefined. The default value of the process-shared attribute is PTHREAD_PROCESS_PRIVATE.
Upon successful completion, the pthread_spin_init() function returns 0. Otherwise, one of the following error codes is returned.
EAGAIN
Description:The system lacks the necessary resources to initialize another spin lock.
EBUSY
Description:The system has detected an attempt to initialize or destroy a spin lock while the lock is in use (for example, while being used in a pthread_spin_lock() call) by another thread.
EINVAL
Description:The value specified by lock is invalid.
Use the pthread_spin_lock(3C) to lock a spin lock. The calling thread acquires the lock if it is not held by another thread. Otherwise, the thread does not return from the pthread_spin_lock() call until the lock becomes available. The results are undefined if the calling thread holds the lock at the time the call is made.
int pthread_spin_lock(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_ spin_lock(&lock); /* lock the spinlock */
Upon successful completion, the pthread_spin_lock() function returns 0. Otherwise, one of the following error codes is returned.
EDEADLK
Description:The current thread already owns the spin lock.
EINVAL
Description:The value specified by lock does not refer to an initialized spin lock object.
Use the pthread_spin_trylock(3C) function to lock a spin lock and fail immediately if the lock is held by another thread.
int pthread_spin_trylock(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_spin_trylock(&lock); /* try to lock the spin lock */
Upon successful completion, the pthread_spin_trylock() function returns 0. Otherwise, one of the following error codes is returned.
EBUSY
Description:A thread currently owns the spin lock.
EINVAL
Description:The value specified by lock does not refer to an initialized spin lock object.
Use the pthread_spin_unlock(3C) function to release a locked spin lock.
int pthread_spin_unlock(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_spin_unlock(&lock); /* spinlock is unlocked */
Upon successful completion, the pthread_spin_unlock() function returns 0. Otherwise, one of the following error codes is returned.
EPERM
Description:The calling thread does not hold the lock.
EINVAL
Description:The value specified by lock does not refer to an initialized spin lock object.
Use the pthread_spin_destroy(3C) function to destroy a spin lock and release any resources used by the lock.
int pthread_spin_destroy(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_spin_destroy(&lock); /* spinlock is destroyed */
The effect of subsequent use of the lock is undefined until the lock is reinitialized by another call to pthread_spin_init(). The results are undefined if pthread_spin_destroy() is called when a thread holds the lock, or if this function is called with an uninitialized thread spin lock.
EBUSY
Description:The system has detected an attempt to initialize or destroy a spin lock while the lock is in use (for example, while being used in a pthread_spin_lock() call) by another thread.
EINVAL
Description:The value specified by lock is invalid.
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, that thread can signal the associated condition variable to cause one or more waiting threads to perform the following actions:
Wake up
Acquire the mutex again
Re-evaluate the condition
Condition variables can be used to synchronize threads among processes in the following situations:
The threads are allocated in memory that can be written to
The memory is shared by the cooperating processes
The scheduling policy determines how blocking threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.
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 |
Function Description |
---|---|
Initialize a condition variable attribute | |
Remove a condition variable attribute | |
Set the scope of a condition variable | |
Get the scope of a condition variable | |
Get the clock selection condition variable attribute | |
Set the clock selection condition variable attribute |
Use pthread_condattr_init(3C) to initialize attributes that are associated with this object to their default values. Storage for each attribute object is allocated by the threads system during execution.
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);
The default value of the pshared attribute when this function is called is PTHREAD_PROCESS_PRIVATE. This value of pshared means that the initialized condition variable can be used within a process.
cattr is an opaque data type that contains a system-allocated attribute object. The possible values of cattr's scope are PTHREAD_PROCESS_PRIVATE and PTHREAD_PROCESS_SHARED . PTHREAD_PROCESS_PRIVATE is the default value.
Before a condition variable attribute can be reused, the attribute must first be reinitialized by pthread_condattr_destroy(3C). The pthread_condattr_init() call returns a pointer to an opaque object. If the object is not destroyed, a memory leak results.
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
Description:Insufficient memory allocated to initialize the thread attributes object.
EINVAL
Description:The value specified by cattr is invalid.
Use pthread_condattr_destroy(3C) to remove storage and render the attribute object invalid.
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);
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
Description:The value specified by cattr is invalid.
pthread_condattr_setpshared(3C) sets the scope of a condition variable to either process private (intraprocess) or system wide (interprocess).
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);
A condition variable created with the pshared attribute set in shared memory to PTHREAD_PROCESS_SHARED, can be shared among threads from more than one process.
If the mutex pshared attribute is set to PTHREAD_PROCESS_PRIVATE, only those threads created by the same process can operate on the mutex. PTHREAD_PROCESS_PRIVATE is the default value. PTHREAD_PROCESS_PRIVATE behaves like a local condition variable. The behavior of PTHREAD_PROCESS_SHARED is equivalent to a global condition variable.
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
Description:The value of cattr is invalid, or the pshared value is invalid.
pthread_condattr_getpshared(3C) gets the current value of pshared for the attribute object cattr.
int pthread_condattr_getpshared(const pthread_condattr_t *restrict cattr, int *restrict pshared);
#include <pthread.h> pthread_condattr_t cattr; int pshared; int ret; /* get pshared value of condition variable */ ret = pthread_condattr_getpshared(&cattr, &pshared);
The value of the attribute object is either PTHREAD_PROCESS_SHARED or PTHREAD_PROCESS_PRIVATE.
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
Description:The value of cattr is invalid.
Use the pthread_condattr_setclock(3C) function to set the clock attribute in an initialized attributes object referenced by attr. If pthread_condattr_setclock() is called with a clock_id argument that refers to a CPU-time clock, the call fails. The clock attribute is the clock ID of the clock that is used to measure the timeout service of pthread_cond_timedwait(). The default value of the clock attribute refers to the system clock, CLOCK_REALTIME. At this time, the only other possible value for the clock attribute is CLOCK_MONOTONIC.
int pthread_condattr_setclock(pthread_condattr_t attr, clockid_t clock_id);
#include <pthread.h> pthread_condattr_t attr clockid_t clock_id int ret ret = pthread_condattr_setclock(&attr &clock_id
pthread_condattr_setclock() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.
EINVAL
Description:The value specified by clock_id does not refer to a known clock, or is a CPU-time clock.
Use the pthread_condattr_getclock(3C) function to obtain the value of the clock attribute from the attributes object referenced by attr. The clock attribute is the clock ID of the clock that is used to measure the timeout service of pthread_cond_timedwait().
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
#include <pthread.h> pthread_condattr_t attr clockid_t clock_id int ret ret = pthread_condattr_getclock(&attr &clock_id
pthread_condattr_getclock() returns zero after completing successfully and stores the value of the clock attribute of attr into the object referenced by the clock_id argument. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.
EINVAL
Description:The value of attr is invalid.
This section explains how to use condition variables. Table 4–5 lists the functions that are available.
Table 4–5 Condition Variables Functions
Operation |
Related Function Description |
---|---|
Initialize a condition variable | |
Block on a condition variable | |
Unblock a specific thread | |
Block until a specified time | |
Block for a specified interval | |
Unblock all threads | |
Destroy condition variable state |
Use pthread_cond_init(3C) to initialize the condition variable pointed at by cv to its default value, or to specify condition variable attributes that are already set with pthread_condattr_init().
int pthread_cond_init(pthread_cond_t *restrict cv, const pthread_condattr_t *restrict cattr);
#include <pthread.h> pthread_cond_t cv; pthread_condattr_t cattr; int ret; /* initialize a condition variable to its default value */ ret = pthread_cond_init(&cv, NULL); /* initialize a condition variable */ ret = pthread_cond_init(&cv, &cattr);
The effect of cattr set to NULL is the same as passing the address of a default condition variable attribute object, but without the memory overhead.
Use the macro PTHREAD_COND_INITIALIZER to initialize statically defined condition variables to their default attributes. The PTHREAD_COND_INITIALIZER macro has the same effect as dynamically allocating pthread_cond_init() with null attributes. No error checking is done.
Multiple threads must not simultaneously initialize or reinitialize the same condition variable. If a condition variable is reinitialized or is destroyed, the application must be sure that the condition variable is not in use.
pthread_cond_init() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.
EINVAL
Description:The value specified by cattr is invalid.
EBUSY
Description:The condition variable is being used.
EAGAIN
Description:The necessary resources are not available.
ENOMEM
Description:Insufficient memory exists to initialize the condition variable.
Use pthread_cond_wait(3C) to atomically release the mutex pointed to by mp and to cause the calling thread to block on the condition variable pointed to by cv.
int pthread_cond_wait(pthread_cond_t *restrict cv,pthread_mutex_t *restrict mutex);
#include <pthread.h> pthread_cond_t cv; pthread_mutex_t mp; int ret; /* wait on condition variable */ ret = pthread_cond_wait(&cv, & mp);
The blocked thread can be awakened by a pthread_cond_signal() , a pthread_cond_broadcast(), or when interrupted by delivery of a signal.
Any change in the value of a condition that is associated with the condition variable cannot be inferred by the return of pthread_cond_wait(). Such conditions must be reevaluated.
The pthread_cond_wait() routine always returns with the mutex locked and owned by the calling thread, even when returning an error.
This function blocks until the condition is signaled. The function atomically releases the associated mutex lock before blocking, and atomically acquires the mutex again before returning.
In typical use, a condition expression is evaluated under the protection of a mutex lock. When the condition expression is false, the thread blocks on the condition variable. The condition variable is then signaled by another thread when the thread changes the condition value. The change causes at least one thread that is waiting on the condition variable to unblock and to reacquire the mutex.
The condition that caused the wait must be retested before continuing execution from the point of the pthread_cond_wait(). The condition could change before an awakened thread reacquires the mutes and returns from pthread_cond_wait(). A waiting thread could be awakened spuriously. The recommended test method is to write the condition check as a while() loop that calls pthread_cond_wait().
pthread_mutex_lock(); while(condition_is_false) pthread_cond_wait(); pthread_mutex_unlock();
The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.
pthread_cond_wait() is a cancellation point. If a cancel is pending and the calling thread has cancellation enabled, the thread terminates and begins executing its cleanup handlers while continuing to hold the lock.
pthread_cond_wait() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.
EINVAL
Description:The value specified by cv or mp is invalid.
Use pthread_cond_signal(3C) to unblock one thread that is blocked on the condition variable pointed to by cv.
int pthread_cond_signal(pthread_cond_t *cv);
#include <pthread.h> pthread_cond_t cv; int ret; /* one condition variable is signaled */ ret = pthread_cond_signal(&cv);
Modify the associated condition under the protection of the same mutex used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait.
The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.
When no threads are blocked on the condition variable, calling pthread_cond_signal() has no effect.
pthread_mutex_t count_lock; pthread_cond_t count_nonzero; unsigned count; decrement_count() { pthread_mutex_lock(&count_lock); while (count == 0) pthread_cond_wait(&count_nonzero, &count_lock); count = count - 1; pthread_mutex_unlock(&count_lock); } increment_count() { pthread_mutex_lock(&count_lock); if (count == 0) pthread_cond_signal(&count_nonzero); count = count + 1; pthread_mutex_unlock(&count_lock); }
pthread_cond_signal() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.
EINVAL
Description:cv points to an illegal address.
Example 4–8 shows how to use pthread_cond_wait() and pthread_cond_signal().
Use pthread_cond_timedwait(3C) as you would use pthread_cond_wait(), except that pthread_cond_timedwait() does not block past the time of day specified by abstime .
int pthread_cond_timedwait(pthread_cond_t *restrict cv, pthread_mutex_t *restrict mp, const struct timespec *restrict abstime);
#include <pthread.h> #include <time.h> pthread_cond_t cv; pthread_mutex_t mp; timestruct_t abstime; int ret; /* wait on condition variable */ ret = pthread_cond_timedwait(&cv, & mp, &abstime);
pthread_cond_timedwait() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_timedwait() is returning an error.
The pthread_cond_timedwait() function blocks until the condition is signaled or until the time of day specified by the last argument has passed.
pthread_cond_timedwait() is also a cancellation point.
pthread_timestruc_t to; pthread_mutex_t m; pthread_cond_t c; ... pthread_mutex_lock(&m); clock_gettime(CLOCK_REALTIME, &to); to.tv_sec += TIMEOUT; while (cond == FALSE) { err = pthread_cond_timedwait(&c, &m, &to); if (err == ETIMEDOUT) { /* timeout, do something */ break; } } pthread_mutex_unlock(&m);
pthread_cond_timedwait() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value.
EINVAL
Description:cv, mp, or abstime points to an illegal address.
EINVAL
Description:Different mutexes were supplied for concurrent pthread_cond_timedwait() operations on the same condition variable.
ETIMEDOUT
Description:The time specified by abstime has passed.
EPERM
Description:The mutex was not owned by the current thread at the time of the call.
The timeout is specified as a time of day so that the condition can be retested efficiently without recomputing the value, as shown in Example 4–9.
Use pthread_cond_reltimedwait_np(3C) as you would use pthread_cond_timedwait() with one exception. pthread_cond_reltimedwait_np() takes a relative time interval rather than an absolute future time of day as the value of its last argument.
int pthread_cond_reltimedwait_np(pthread_cond_t *cv, pthread_mutex_t *mp, const struct timespec *reltime);
#include <pthread.h> #include <time.h> pthread_cond_t cv; pthread_mutex_t mp; timestruct_t reltime; int ret; /* wait on condition variable */ ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime);
pthread_cond_reltimedwait_np() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_reltimedwait_np() is returning an error. The pthread_cond_reltimedwait_np() function blocks until the condition is signaled or until the time interval specified by the last argument has elapsed.
pthread_cond_reltimedwait_np() is also a cancellation point.
pthread_cond_reltimedwait_np() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value.
EINVAL
Description:The value specified by reltime is invalid.
ETIMEDOUT
Description:The time interval specified by reltime has passed.
Use pthread_cond_broadcast(3C) to unblock all threads that are blocked on the condition variable pointed to by cv, specified by pthread_cond_wait().
int pthread_cond_broadcast(pthread_cond_t *cv);
#include <pthread.h> pthread_cond_t cv; int ret; /* all condition variables are signaled */ ret = pthread_cond_broadcast(&cv);
When no threads are blocked on the condition variable, pthread_cond_broadcast() has no effect.
Since pthread_cond_broadcast() causes all threads blocked on the condition to contend again for the mutex lock, use pthread_cond_broadcast() with care. For example, use pthread_cond_broadcast() to allow threads to contend for varying resource amounts when resources are freed, as shown in Example 4–10.
pthread_mutex_t rsrc_lock; pthread_cond_t rsrc_add; unsigned int resources; get_resources(int amount) { pthread_mutex_lock(&rsrc_lock); while (resources < amount) { pthread_cond_wait(&rsrc_add, &rsrc_lock); } resources -= amount; pthread_mutex_unlock(&rsrc_lock); } add_resources(int amount) { pthread_mutex_lock(&rsrc_lock); resources += amount; pthread_cond_broadcast(&rsrc_add); pthread_mutex_unlock(&rsrc_lock); }
Note that in add_resources() whether resources are updated first, or if pthread_cond_broadcast() is called first inside the mutex lock does not matter.
Modify the associated condition under the protection of the same mutex that is used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait.
pthread_cond_broadcast() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.
EINVAL
Description:cv points to an illegal address.
Use pthread_cond_destroy(3C) to destroy any state that is associated with the condition variable pointed to by cv.
int pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h> pthread_cond_t cv; int ret; /* Condition variable is destroyed */ ret = pthread_cond_destroy(&cv);
Note that the space for storing the condition variable is not freed.
pthread_cond_destroy() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.
EINVAL
Description:The value specified by cv is invalid.
A call to pthread_cond_signal() or pthread_cond_broadcast() when the thread does not hold the mutex lock associated with the condition can lead to lost wake-up bugs.
A lost wake-up occurs when all of the following conditions are in effect:
A thread calls pthread_cond_signal() or pthread_cond_broadcast()
Another thread is between the test of the condition and the call to pthread_cond_wait()
No threads are waiting
The signal has no effect, and therefore is lost
This can occur only if the condition being tested is modified without holding the mutex lock associated with the condition. As long as the condition being tested is modified only while holding the associated mutex, pthread_cond_signal() and pthread_cond_broadcast() can be called regardless of whether they are holding the associated mutex.
The producer and consumer problem is one of the small collection of standard, well-known problems in concurrent programming. A finite-size buffer and two classes of threads, producers and consumers, put items into the buffer (producers) and take items out of the buffer (consumers).
A producer cannot put something in the buffer until the buffer has space available. A consumer cannot take something out of the buffer until the producer has written to the buffer.
A condition variable represents a queue of threads that wait for some condition to be signaled.
Example 4–11 has two such queues. One (less) queue for producers waits for a slot in the buffer. The other (more) queue for consumers waits for a buffer slot containing information. The example also has a mutex, as the data structure describing the buffer must be accessed by only one thread at a time.
typedef struct { char buf[BSIZE]; int occupied; int nextin; int nextout; pthread_mutex_t mutex; pthread_cond_t more; pthread_cond_t less; } buffer_t; buffer_t buffer;
As Example 4–12 shows, the producer thread acquires the mutex protecting the buffer data structure. The producer thread then makes certain that space is available for the item produced. If space is not available, the producer thread calls pthread_cond_wait() . pthread_cond_wait() causes the producer thread to join the queue of threads that are waiting for the condition less to be signaled. less represents available room in the buffer.
At the same time, as part of the call to pthread_cond_wait(), the thread releases its lock on the mutex. The waiting producer threads depend on consumer threads to signal when the condition is true, as shown in Example 4–12. When the condition is signaled, the first thread waiting on less is awakened. However, before the thread can return from pthread_cond_wait(), the thread must acquire the lock on the mutex again.
Acquire the mutex to ensure that the thread again has mutually exclusive access to the buffer data structure. The thread then must check that available room in the buffer actually exists. If room is available, the thread writes into the next available slot.
At the same time, consumer threads might be waiting for items to appear in the buffer. These threads are waiting on the condition variable more . A producer thread, having just deposited something in the buffer, calls pthread_cond_signal() to wake up the next waiting consumer. If no consumers are waiting, this call has no effect.
Finally, the producer thread unlocks the mutex, allowing other threads to operate on the buffer data structure.
void producer(buffer_t *b, char item) { pthread_mutex_lock(&b->mutex); while (b->occupied >= BSIZE) pthread_cond_wait(&b->less, &b->mutex); assert(b->occupied < BSIZE); b->buf[b->nextin++] = item; b->nextin %= BSIZE; b->occupied++; /* now: either b->occupied < BSIZE and b->nextin is the index of the next empty slot in the buffer, or b->occupied == BSIZE and b->nextin is the index of the next (occupied) slot that will be emptied by a consumer (such as b->nextin == b->nextout) */ pthread_cond_signal(&b->more); pthread_mutex_unlock(&b->mutex); }
Note the use of the assert() statement. Unless the code is compiled with NDEBUG defined, assert() does nothing when its argument evaluates to true (nonzero). The program aborts if the argument evaluates to false (zero). Such assertions are especially useful in multithreaded programs. assert() immediately points out runtime problems if the assertion fails. assert() has the additional effect of providing useful comments.
The comment that begins /* now: either b->occupied ... could better be expressed as an assertion, but the statement is too complicated as a Boolean-valued expression and so is given in English.
Both assertions and comments are examples of invariants. These invariants are logical statements that should not be falsified by the execution of the program with the following exception. The exception occurs during brief moments when a thread is modifying some of the program variables mentioned in the invariant. An assertion, of course, should be true whenever any thread executes the statement.
The use of invariants is an extremely useful technique. Even if the invariants are not stated in the program text, think in terms of invariants when you analyze a program.
The invariant in the producer code that is expressed as a comment is always true whenever a thread executes the code where the comment appears. If you move this comment to just after the mutex_unlock(), the comment does not necessarily remain true. If you move this comment to just after the assert() , the comment is still true.
This invariant therefore expresses a property that is true at all times with the following exception. The exception occurs when either a producer or a consumer is changing the state of the buffer. While a thread is operating on the buffer under the protection of a mutex, the thread might temporarily falsify the invariant. However, once the thread is finished, the invariant should be true again.
Example 4–13 shows the code for the consumer. The logic flow is symmetric with the logic flow of the producer.
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); }
A semaphore is 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 where a single track is present over which only one train at a time is allowed.
A semaphore synchronizes travel on this track. 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 the thread has proceeded by performing a P operation on the semaphore.
The thread must wait until the semaphore's value is positive, then change the semaphore's value by subtracting 1 from the value. When this operation is finished, the thread performs a V operation, which changes the semaphore's value by adding 1 to the value. These operations must take place atomically. These operations 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 the value is decremented, resulting in a value that is guaranteed to be nonnegative and 1 less than what it was before it was decremented.
In both P and V operations, the arithmetic must take place without interference. The net effect of two V operations performed simultaneously on the same semaphore, should be that the semaphore's new value is 2 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. The mnemonic significance 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 to returns immediately with a nonzero value.
The two basic sorts of semaphores are binary semaphores and counting semaphores. Binary semaphores never take on values other than zero or one, and counting semaphores take on arbitrary nonnegative values. A binary semaphore is logically just like a mutex.
However, although not always enforced, mutexes should be unlocked only by the thread that holds the lock. Because no notion exists of “the thread that holds the semaphore,” any thread can perform a V or sem_post (3RT) operation.
Counting semaphores are nearly as powerful as conditional variables when used in conjunction with mutexes. In many cases, the code might be simpler when implemented with counting semaphores rather than with condition variables, as shown in Example 4–14, Example 4–15, and Example 4–16.
However, when a mutex is used with condition variables, an implied bracketing is present. The bracketing clearly delineates which part of the program is being protected. This behavior is not necessarily the case for a semaphore, which might be called the go to of concurrent programming. A semaphore is powerful but too easy to use in an unstructured, indeterminate way.
POSIX semaphores can be unnamed or named. Unnamed semaphores are allocated in process memory and initialized. Unnamed semaphores might be usable by more than one process, depending on how the semaphore is allocated and initialized. Unnamed semaphores are either private, inherited through fork(), or are protected by access protections of the regular file in which they are allocated and mapped.
Named semaphores are like process-shared semaphores, except that named semaphores are referenced with a pathname rather than a pshared value. Named semaphores are sharable by several processes. Named semaphores have an owner user-id, group-id, and a protection mode.
The functions sem_open, sem_getvalue, sem_close, and sem_unlink are available to open, retrieve, close, and remove named semaphores. By using sem_open, you can create a named semaphore that has a name defined in the file system name space.
For more information about named semaphores, see the sem_open, sem_getvalue, sem_close, and sem_unlink man pages.
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, no more resources are present. Threads that try to decrement the semaphore when the count is zero block until the count becomes greater than zero.
Table 4–6 Routines for Semaphores
Operation |
Related Function Description |
---|---|
Initialize a semaphore | |
Increment a semaphore | |
Block on a semaphore count | |
Decrement a semaphore count | |
Destroy the semaphore state |
Because semaphores need not be acquired and be released by the same thread, semaphores can be used for asynchronous event notification, such as in signal handlers. And, because semaphores contain state, semaphores can be used asynchronously without acquiring a mutex lock as is required by condition variables. However, semaphores are not as efficient as mutex locks.
The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.
Semaphores must be initialized before use, however semaphores do not have attributes.
Use sem_init(3RT) to initialize the unnamed semaphore variable pointed to by sem to value amount.
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);
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.
Multiple threads must not initialize the same semaphore.
A semaphore must not be reinitialized while other threads might be using the semaphore.
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);
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);
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
Description:The value argument exceeds SEM_VALUE_MAX .
ENOSPC
Description:A resource that is required to initialize the semaphore has been exhausted. The limit on semaphores SEM_NSEMS_MAX has been reached.
EPERM
Description:The process lacks the appropriate privileges to initialize the semaphore.
Use sem_post(3RT) to atomically increment the semaphore pointed to by sem.
int sem_post(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_post(&sem); /* semaphore is posted */
When any threads are blocked on the semaphore, one of the threads is unblocked.
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
Description:sem points to an illegal address.
Use sem_wait(3RT) to block the calling thread until the semaphore count pointed to by sem becomes greater than zero, then atomically decrement the count.
int sem_wait(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_wait(&sem); /* wait for semaphore */
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
Description:sem points to an illegal address.
EINTR
Description:A signal interrupted this function.
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.
int sem_trywait(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_trywait(&sem); /* try to wait for semaphore*/
This function is a nonblocking version of sem_wait(). sem_trywait() returns immediately if unsuccessful.
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
Description:sem points to an illegal address.
EINTR
Description:A signal interrupted this function.
EAGAIN
Description:The semaphore was already locked, so the semaphore cannot be immediately locked by the sem_trywait() operation.
Use sem_destroy(3RT) to destroy any state that is associated with the unnamed semaphore pointed to by sem.
int sem_destroy(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_destroy(&sem); /* the semaphore is destroyed */
The space for storing the semaphore is not freed.
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
Description:sem points to an illegal address.
The data structure in Example 4–14 is similar to the structure used for the condition variables example, shown in Example 4–11. Two semaphores represent the number of full and empty buffers. The semaphores ensure that producers wait until buffers are empty and that consumers wait until buffers are full.
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. The semaphores control access to the buffer when multiple producers use multiple empty buffer slots, and when multiple consumers use multiple full buffer slots. Mutexes would work better here, but would not provide as good an example of semaphore use.
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); }
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 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. Set up at the beginning of the application makes the attributes easier to locate and modify. The following table lists the functions discussed in this section that manipulate read-write lock attributes.
Table 4–7 Routines for Read-Write Lock Attributes
Operation |
Related Function Description |
---|---|
Initialize a read-write lock attribute | |
Destroy a read-write lock attribute | |
Set a read-write lock attribute | |
Get a read-write lock attribute |
pthread_rwlockattr_init(3C) initializes a read-write lock attributes object attr with the default value for all of the attributes defined by the implementation.
#include <pthread.h> int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
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 initializes one or more read-write locks, any function that affects the object, including destruction, does not affect previously initialized read-write locks.
If successful, pthread_rwlockattr_init() returns zero. Otherwise, an error number is returned to indicate the error.
ENOMEM
Description:Insufficient memory exists to initialize the read-write attributes object.
pthread_rwlockattr_destroy(3C) destroys a read-write lock attributes object.
#include <pthread.h> int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
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.
If successful, pthread_rwlockattr_destroy() returns zero. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The value specified by attr is invalid.
pthread_rwlockattr_setpshared(3C) sets the process-shared read-write lock attribute.
#include <pthread.h> int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
The pshared lock attribute has one of the following values:
PTHREAD_PROCESS_SHARED
Description: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. Operation on the read-write lock is permitted even if the lock is allocated in memory that is shared by multiple processes.
PTHREAD_PROCESS_PRIVATE
Description:The read-write lock is only 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.
If successful, pthread_rwlockattr_setpshared() returns zero. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The value specified by attr or pshared is invalid.
pthread_rwlockattr_getpshared(3C) gets the process-shared read-write lock attribute.
#include <pthread.h> int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
pthread_rwlockattr_getpshared() obtains the value of the process-shared attribute from the initialized attributes object referenced by attr.
If successful, pthread_rwlockattr_getpshared() returns zero. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The value specified by attr or pshared is invalid.
After the attributes for a read-write lock are configured, you initialize the read-write lock. 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–8 Routines that Manipulate Read-Write Locks
Operation |
Related Function Description |
---|---|
Initialize a read-write lock | |
Read lock on read-write lock | |
Read lock with a nonblocking read-write lock | |
Write lock on read-write lock | |
Write lock with a nonblocking read-write lock | |
Unlock a read-write lock | |
Destroy a read-write lock |
Use pthread_rwlock_init(3C) to initialize the read-write lock referenced by rwlock with the attributes referenced by attr.
#include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
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. After the lock is 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.
In cases where default read-write lock attributes are appropriate, the macro PTHREAD_RWLOCK_INITIALIZER can 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.
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
Description:The value specified by attr or rwlock is invalid.
pthread_rwlock_rdlock(3C) applies a read lock to the read-write lock referenced by rwlock.
#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );
The calling thread acquires the read lock if a writer does not hold the lock and no writers are blocked on the lock. Whether the calling thread acquires the lock when a writer does not hold the lock and writers are waiting for the lock is unspecified. If a writer holds the lock, the calling thread does not acquire the read lock. If the read lock is not acquired, the calling thread blocks. The thread does not return from the pthread_rwlock_rdlock() until the thread 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. The Solaris implementation favors writers over readers.
A thread can hold multiple concurrent read locks on rwlock The thread can successfully call pthread_rwlock_rdlock() n times. The thread must call pthread_rwlock_unlock() n times to perform matching unlocks.
Results are undefined if pthread_rwlock_rdlock() is called with an uninitialized read-write lock.
A thread signal handler processes a signal delivered to a thread waiting for a read-write lock. On return from the signal handler, the thread resumes waiting for the read-write lock for reading as if the thread was not interrupted.
If successful, pthread_rwlock_rdlock() returns zero. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The value specified by attr or rwlock is invalid.
The pthread_rwlock_timedrdlock(3C) function applies a read lock to the read-write lock referenced by rwlock as in the pthread_rwlock_rdlock() function.
#include <pthread.h> #include <time.h> int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
If the lock cannot be acquired without waiting for other threads to unlock the lock, this wait will be terminated when the specified timeout expires. The timeout expires when the absolute time specified by abs_timeout passes, as measured by the CLOCK_REALTIME clock (that is, when the value of that clock equals or exceeds abs_timeout), or if the absolute time specified by abs_timeout has already been passed at the time of the call.
The resolution of the timeout is the resolution of the CLOCK_REALTIME clock. The timespec data type is defined in the <time.h> header. Under no circumstances does the function fail with a timeout if the lock can be acquired immediately. The validity of the timeout parameter need not be checked if the lock can be immediately acquired.
If a signal that causes a signal handler to be executed is delivered to a thread blocked on a read-write lock with a call to pthread_rwlock_timedrdlock(), upon return from the signal handler the thread resumes waiting for the lock as if it was not interrupted.
The calling thread might deadlock if at the time the call is made it holds a write lock on rwlock.
The pthread_rwlock_reltimedrdlock_np() function is identical to the pthread_rwlock_timedrdlock() function, except that the timeout is specified as a relative time interval.
If successful, returns 0 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.
ETIMEDOUT
Description:The lock could not be acquired before the specified timeout expired.
EAGAIN
Description:The read lock could not be acquired because the maximum number of read locks for lock would be exceeded.
EDEADLK
Description:The calling thread already holds the rwlock.
EINVAL
Description:The value specified by rwlock does not refer to an initialized read-write lock object, or the timeout nanosecond value is less than zero or greater than or equal to 1,000 million.
pthread_rwlock_tryrdlock(3C) 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 writers are blocked on rwlock.
#include <pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_tryrdlock() returns zero if the lock for reading on the read-write lock object referenced by rwlock is acquired. If the lock is not acquired, an error number is returned to indicate the error.
EBUSY
Description:The read-write lock could not be acquired for reading because a writer holds the lock or was blocked on it.
pthread_rwlock_wrlock(3C) applies a write lock to the read-write lock referenced by rwlock.
#include <pthread.h> int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );
The calling thread acquires the write lock if no other reader thread or writer thread holds the read-write lock rwlock. Otherwise, the thread blocks. The thread does not return from the pthread_rwlock_wrlock() call until the thread can acquire the lock. Results are undefined if the calling thread holds the read-write lock, either a read lock or write lock, at the time the call is made.
Implementations are allowed to favor writers over readers to avoid writer starvation. The Solaris implementation favors writers over readers.
Results are undefined if pthread_rwlock_wrlock() is called with an uninitialized read-write lock.
The thread signal handler processes a signal 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 the thread was not interrupted.
pthread_rwlock_rwlock() returns zero if the lock for writing on the read-write lock object referenced by rwlock is acquired. If the lock is not acquired, an error number is returned to indicate the error.
pthread_rwlock_trywrlock(3C) 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.
#include <pthread.h> int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
Results are undefined if pthread_rwlock_trywrlock() is called with an uninitialized read-write lock.
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
Description:The read-write lock could not be acquired for writing because the read-write lock is already locked for reading or writing.
The pthread_rwlock_timedwrlock(3C) function applies a write lock to the read-write lock referenced by rwlock as in the pthread_rwlock_wrlock() function, but attempts to apply the lock only until a specified absolute time.
#include <pthread.h> #include <time.h> int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
The calling thread acquires the write lock if no other reader thread or writer thread holds the read-write lock rwlock. If the lock cannot be acquired without waiting for other threads to unlock the lock, this wait will be terminated when the specified timeout expires. The timeout expires when the absolute time specified by abs_timeoutpasses, as measured by the CLOCK_REALTIME clock (that is, when the value of that clock equals or exceeds abs_timeout) or if the absolute time specified by abs_timeout has already been passed at the time of the call. The pthread_rwlock_reltimedwrlock_np() function is identical to the pthread_rwlock_timedwrlock() function, except that the timeout is specified as a relative time interval.
If successful, returns 0 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.
ETIMEDOUT
Description:The lock could not be acquired before the specified timeout expired.
EDEADLK
Description:The calling thread already holds the rwlock.
EINVAL
Description:The value specified by rwlock does not refer to an initialized read-write lock object, or the timeout nanosecond value is less than zero or greater than or equal to 1,000 million.
pthread_rwlock_unlock(3C) releases a lock held on the read-write lock object referenced by rwlock.
#include <pthread.h> int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
Results are undefined if the read-write lock rwlock is not held by the calling thread.
If pthread_rwlock_unlock() is called to release a read lock from the read-write lock object, and other read locks are currently held on this lock object, the 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, the calling thread is no longer an owner of the object. If pthread_rwlock_unlock() releases the last read lock for this read-write lock object, the read-write lock object is 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 lock object is put in the unlocked state with no owners.
If pthread_rwlock_unlock() unlocks the read-write lock object and multiple threads are waiting to acquire the lock object for writing, the scheduling policy determines which thread acquires the object for writing. If multiple threads are waiting to acquire the read-write lock object for reading, the scheduling policy determines the order the waiting threads acquire the object for reading. If multiple threads are blocked on rwlock for both read locks and write locks, whether the readers or the writer acquire the lock first is unspecified.
Results are undefined if pthread_rwlock_unlock() is called with an uninitialized read-write lock.
If successful, pthread_rwlock_unlock() returns zero. Otherwise, an error number is returned to indicate the error.
pthread_rwlock_destroy(3C) destroys the read-write lock object referenced by rwlock and releases any resources used by the lock.
#include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock_t **rwlock);
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 the lock object has been destroyed are undefined.
If successful, pthread_rwlock_destroy() returns zero. Otherwise, an error number is returned to indicate the error.
EINVAL
Description:The value specified by attr or rwlock is invalid.
In cases where you must wait for a number of tasks to be completed before an overall task can proceed, barrier synchronization can be used. POSIX threads specifies a synchronization object called a barrier, along with barrier functions. The functions create the barrier, specifying the number of threads that are synchronizing on the barrier, and set up threads to perform tasks and wait at the barrier until all the threads reach the barrier. When the last thread arrives at the barrier, all the threads resume execution.
See Parallelizing a Loop on a Shared-Memory Parallel Computer for more about barrier synchronization.
Use pthread_barrier_init(3C) to allocate resources for a barrier and initialize its attributes.
int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
#include <pthread.h> pthread_barrier_t barrier; pthread_barrierattr_t attr; unsigned count; int ret; ret = pthread_barrier_init(&barrier, &attr, count);
The pthread_barrier_init() function allocates any resources required to use the barrier referenced by barrier and initializes the barrier with attributes referenced by attr. If attr is NULL, the default barrier attributes are used; the effect is the same as passing the address of a default barrier attributes object. The count argument specifies the number of threads that must call pthread_barrier_wait() before any of them successfully return from the call. The value specified by count must be greater than 0.
pthread_barrier_init() 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
Description:The value specified by count is equal to 0, or the value specified by attr is invalid
EAGAIN
Description:The system lacks the necessary resources to initialize another barrier.
ENOMEM
Description:Insufficient memory exists to initialize the barrier.
EBUSY
Description:There was an attempt to destroy a barrier while it is in use (for example, while being used in a pthread_barrier_wait() call) by another thread.
Use pthread_barrier_wait(3C) to synchronize threads at a specified barrier. The calling thread blocks until the required number of threads have called pthread_barrier_wait() specifying the barrier. The number of threads is specified in the pthread_barrier_init() function.
When the required number of threads have called pthread_barrier_wait() specifying the barrier, the constant PTHREAD_BARRIER_SERIAL_THREAD is returned to one unspecified thread and 0 is returned to each of the remaining threads. The barrier is then reset to the state it had as a result of the most recent pthread_barrier_init() function that referenced it.
int pthread_barrier_wait(pthread_barrier_t *barrier);
#include <pthread.h> pthread_barrier_t barrier; int ret; ret = pthread_barrier_wait(&barrier);
When pthread_barrier_wait() completes successfully, the function returns PTHREAD_BARRIER_SERIAL_THREAD, which is defined in pthread.h, for one arbitrary thread synchronized at the barrier. The function returns zero for each of the other threads. Otherwise an error code is returned.
EINVAL
Description:The value specified by barrier does not refer to an initialized barrier object.
When a barrier is no longer needed, it should be destroyed. Use the pthread_barrier_destroy(3C) function to destroy the barrier referenced by barrier and release any resources used by the barrier.
int pthread_barrier_destroy(pthread_barrier_t *barrier);
#include <pthread.h> pthread_barrier_t barrier; int ret; ret = pthread_barrier_destroy(&barrier);
pthread_barrier_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
Description:Indicates that the value of barrier was not valid.
EBUSY
Description:An attempt was made to destroy a barrier while it is in use (for example, while being used in a pthread_barrier_wait() by another thread.
The pthread_barrierattr_init(3C) function initializes a barrier attributes object attr with the default values for the attributes defined for the object by the implementation. Currently, only the process-shared attribute is provided, and the pthread_barrierattr_getpshared() and pthread_barrierattr_setpshared() functions are used to get and set the attribute.
After a barrier attributes object has been used to initialize one or more barriers, any function affecting the attributes object (including destruction) does not affect any previously initialized barrier.
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
#include <pthread.h> pthread_barrierattr_t attr; int ret; ret = pthread_barrierattr_init(&attr);
pthread_barrierattr_init() 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.
ENOMEM
Description:Insufficient memory exists to initialize the barrier attributes object.
The pthread_barrierattr_setpshared() function sets the process-shared attribute in an initialized attributes object referenced by attr. The process-shared attribute can have the following values:
The barrier can only be operated upon by threads created within the same process as the thread that initialized the barrier. This is the default value of the process-shared attribute.
The barrier can be operated upon by any thread that has access to the memory where the barrier is allocated.
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
pthread_barrierattr_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
Description:Indicates that the value of attr was not valid, or the new value specified for the pshared is not valid.
The pthread_barrierattr_getpshared(3C) function obtains the value of the process-shared attribute from the attributes object referenced by attr. The value is set by the pthread_barrierattr_setpshared() function.
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
pthread_barrierattr_getpshared() returns zero after completing successfully, and stores the value of the process-shared attribute of attr into the object referenced by the pshared parameter. Any other return value indicates that an error occurred. If the following condition occurs, the function fails and returns the corresponding value.
EINVAL
Description:Indicates that the value of attr was not valid.
The pthread_barrierattr_destroy() function destroys a barrier attributes object. A destroyed attr attributes object can be reinitialized using pthread_barrierattr_init().
After a barrier attributes object has been used to initialize one or more barriers, destroying the object does not affect any previously initialized barrier.
#include <pthread.h> int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
pthread_barrierattr_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
Description:Indicates that the value of attr was not valid.
Each of the synchronization primitives can be used across process boundaries. The primitives are set up by ensuring that the synchronization variable is located in a shared memory segment and by calling the appropriate init() routine. The primitive must have been initialized with its shared attribute set to interprocess.
Example 4–17 shows the producer and consumer problem with the producer and consumer in separate processes. The main routine maps zero-filled memory shared with its child process into its address space.
A child process is created to run the consumer. The parent runs the producer.
This example also shows the drivers for the producer and consumer. The producer_driver() 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 the structure used for the condition variables example, shown in Example 4–4. Two semaphores represent the number of full and empty buffers. The semaphores ensure that producers wait for empty buffers and that consumers wait until the buffers are full.
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); } }
The most basic synchronization primitive in threads is the mutual exclusion lock. So, mutual exclusion lock 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. The condition variable 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. The semaphore is easier to use in some circumstances because a semaphore variable operates 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.