Contained WithinFind More DocumentationFeatured Support Resources | PDF로 이 문서 다운로드 (1549 KB)
Chapter 4 Programming with Synchronization ObjectsThis 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
Situations that can benefit from the use of synchronization include the following:
Note –
On 32-bit architectures, a Mutual Exclusion Lock AttributesUse 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
Initializing a Mutex Attribute ObjectUse 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. pthread_mutexattr_init Syntaxint 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 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
pthread_mutexattr_init Return Valuespthread_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. Destroying a Mutex Attribute Objectpthread_mutexattr_destroy(3C) deallocates the storage space used to maintain the attribute object created by pthread_mutexattr_init(). pthread_mutexattr_destroy Syntaxint 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 Return Valuespthread_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. Setting the Scope of a Mutexpthread_mutexattr_setpshared(3C) sets the scope of the mutex variable. pthread_mutexattr_setpshared Syntaxint 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 Return Valuespthread_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. Getting the Scope of a Mutexpthread_mutexattr_getpshared(3C) returns the scope of the mutex variable defined by pthread_mutexattr_setpshared(). pthread_mutexattr_getpshared Syntaxint 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 Return Valuespthread_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. Setting the Mutex Type Attributepthread_mutexattr_settype(3C) sets the mutex type attribute. pthread_mutexattr_settype Syntax#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. pthread_mutexattr_settype Return ValuesIf 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. Getting the Mutex Type Attributepthread_mutexattr_gettype(3C) gets the mutex type attribute set by pthread_mutexattr_settype(). pthread_mutexattr_gettype Syntax#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
For a description of each type, see pthread_mutexattr_settype Syntax. pthread_mutexattr_gettype Return ValuesOn 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. Setting the Mutex Attribute's Protocolpthread_mutexattr_setprotocol(3C) sets the protocol attribute of a mutex attribute object. pthread_mutexattr_setprotocol Syntax#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 .
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. pthread_mutexattr_setprotocol Return ValuesOn 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. Getting the Mutex Attribute's Protocolpthread_mutexattr_getprotocol(3C) gets the protocol attribute of a mutex attribute object. pthread_mutexattr_getprotocol Syntax#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>. pthread_mutexattr_getprotocol Return ValuesOn 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. Setting the Mutex Attribute's Priority Ceilingpthread_mutexattr_setprioceiling(3C) sets the priority ceiling attribute of a mutex attribute object. pthread_mutexattr_setprioceiling Syntax#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. pthread_mutexattr_setprioceiling Return ValuesOn 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. Getting the Mutex Attribute's Priority Ceilingpthread_mutexattr_getprioceiling(3C) gets the priority ceiling attribute of a mutex attribute object. pthread_mutexattr_getprioceiling Syntax#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. pthread_mutexattr_getprioceiling Return ValuesOn 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. Setting the Mutex's Priority Ceilingpthread_mutexattr_setprioceiling(3C) sets the priority ceiling of a mutex. pthread_mutex_setprioceiling Syntax#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. pthread_mutex_setprioceiling Return ValuesOn 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. Getting the Mutex's Priority Ceilingpthread_mutexattr_getprioceiling(3C) gets the priority ceiling of a mutex. pthread_mutex_getprioceiling Syntax#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. pthread_mutex_getprioceiling Return ValuesOn 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. Setting the Mutex's Robust Attributepthread_mutexattr_setrobust_np(3C) sets the robust attribute of a mutex attribute object. pthread_mutexattr_setrobust_np Syntax#include <pthread.h> int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, int *robustness); Note – pthread_mutexattr_setrobust_np() applies only if the symbol _POSIX_THREAD_PRIO_INHERIT is defined. 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_mutexattr_setrobust_np Return ValuesOn 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. Getting the Mutex's Robust Attributepthread_mutexattr_getrobust_np(3C) gets the robust attribute of a mutex attribute object. pthread_mutexattr_getrobust_np Syntax#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. pthread_mutexattr_getrobust_np Return ValuesOn 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. Using Mutual Exclusion LocksTable 4–3 lists the functions that manipulate mutex locks. Table 4–3 Routines for Mutual Exclusion Locks
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. Initializing a MutexUse 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 . pthread_mutex_init Syntaxint 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. Note – 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 Return Valuespthread_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. Making a Mutex ConsistentIf 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. pthread_mutex_consistent_np Syntax#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 Return Valuespthread_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. Locking a MutexUse pthread_mutex_lock(3C) to lock the mutex pointed to by mutex. pthread_mutex_lock Syntaxint 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 Return Valuespthread_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: ENOMEM Description:The limit on the number of simultaneously held mutexes has been exceeded. Unlocking a MutexUse pthread_mutex_unlock(3C) to unlock the mutex pointed to by mutex. pthread_mutex_unlock Syntaxint 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 Return Valuespthread_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. Locking a Mutex Without BlockingUse pthread_mutex_trylock(3C) to attempt to lock the mutex pointed to by mutex, and return immediately if the mutex is already locked. pthread_mutex_trylock Syntaxint 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 Return Valuespthread_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. Locking a Mutex Before a Specified Absolute TimeUse 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. pthread_mutex_timedlock() Syntaxint 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); pthread_mutex_timedlock() Return ValuesThe 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. Locking a Mutex Within a Specified Time IntervalUse 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. pthread_mutex_reltimedlock_np() Syntaxint 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); pthread_mutex_reltimedlock_np() Return ValuesThe 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. Destroying a MutexUse pthread_mutex_destroy(3C) to destroy any state that is associated with the mutex pointed to by mp . pthread_mutex_destroy Syntaxint 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 Return Valuespthread_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. Code Examples of Mutex LockingExample 4–1 shows some code fragments with mutex locking. Example 4–1 Mutex Lock Example#include <pthread.h>
pthread_mutex_t count_mutex;
long long count;
void
increment_count()
{
pthread_mutex_lock(&count_mutex);
count = count + 1;
pthread_mutex_unlock(&count_mutex);
}
long long
get_count()
{
long long c;
pthread_mutex_lock(&count_mutex);
c = count;
pthread_mutex_unlock(&count_mutex);
return (c);
}
The two functions in Example 4–1 use
the mutex lock for different purposes. The increment_count()
function uses the mutex lock 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 When you read an integer value, the operation is atomic because an integer is the common word size on most machines. Examples of Using Lock HierarchiesOccasionally, 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. Example 4–2 Deadlock
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. Example 4–3 Conditional Locking
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. Examples of Using Nested Locking With a Singly-Linked ListExample 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. Example 4–4 Singly-Linked List Structuretypedef 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. Example 4–5 Singly-Linked List With Nested Lockingnode1_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 of Nested Locking With a Circularly-Linked ListExample 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. Example 4–6 Circular-Linked List Structuretypedef 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. Example 4–7 Circular Linked List With Nested Lockingvoid 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);
}
Using Spin LocksSpin 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. Initializing a Spin LockUse the pthread_spin_init(3C) function to allocate resources required to use a spin lock, and initialize the lock to an unlocked state. pthread_spin_init() Syntaxint 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. pthread_spin_init() Return ValuesUpon 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. Acquiring a Spin LockUse 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. pthread_spin_lock() Syntaxint pthread_spin_lock(pthread_spinlock_t *lock); #include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_ spin_lock(&lock); /* lock the spinlock */ pthread_spin_lock() Return ValuesUpon 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. Acquiring a Non-Blocking Spin LockUse the pthread_spin_trylock(3C) function to lock a spin lock and fail immediately if the lock is held by another thread. pthread_spin_trylock() Syntaxint 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 */ pthread_spin_trylock() Return ValuesUpon 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. Unlocking a Spin LockUse the pthread_spin_unlock(3C) function to release a locked spin lock. pthread_spin_unlock() Syntaxint pthread_spin_unlock(pthread_spinlock_t *lock); #include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_spin_unlock(&lock); /* spinlock is unlocked */ pthread_spin_unlock() Return ValuesUpon 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. Destroying a Spin LockUse the pthread_spin_destroy(3C) function to destroy a spin lock and release any resources used by the lock. pthread_spin_destroy() Syntaxint 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. pthread_spin_destroy() Return ValuesEBUSY 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. Condition Variable AttributesUse 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:
Condition variables can be used to synchronize threads among processes in the following situations:
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
Initializing a Condition Variable AttributeUse 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. pthread_condattr_init Syntaxint 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 Return Valuespthread_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. Removing a Condition Variable AttributeUse pthread_condattr_destroy(3C) to remove storage and render the attribute object invalid. pthread_condattr_destroy Syntaxint 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 Return Valuespthread_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. Setting the Scope of a Condition Variablepthread_condattr_setpshared(3C) sets the scope of a condition variable to either process private (intraprocess) or system wide (interprocess). pthread_condattr_setpshared Syntaxint 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 Return Valuespthread_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. Getting the Scope of a Condition Variablepthread_condattr_getpshared(3C) gets the current value of pshared for the attribute object cattr. pthread_condattr_getpshared Syntaxint 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 Return Valuespthread_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. Setting the Clock Selection Condition VariableUse 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. pthread_condattr_setclock Syntaxint 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 Returnspthread_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. Getting the Clock Selection Condition VariableUse 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(). pthread_condattr_getclock Syntaxint 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 Returnspthread_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. Using Condition VariablesThis section explains how to use condition variables. Table 4–5 lists the functions that are available. Table 4–5 Condition Variables Functions
Initializing a Condition VariableUse pthread_cond_init(3C) to initialize the condition variable pointed at by cv to its default value, or to specify condition variable attributes that are already set with pthread_condattr_init(). pthread_cond_init Syntaxint pthread_cond_init(pthread_cond_t *restrict cv,
const pthread_condattr_t *restrict cattr);
#include <pthread.h> pthread_cond_t cv; pthread_condattr_t cattr; int ret; /* initialize a condition variable to its default value */ ret = pthread_cond_init(&cv, NULL); /* initialize a condition variable */ ret = pthread_cond_init(&cv, &cattr); The effect of cattr set to NULL is the same as passing the address of a default condition variable attribute object, but without the memory overhead. Use the macro PTHREAD_COND_INITIALIZER to initialize statically defined condition variables to their default attributes. The PTHREAD_COND_INITIALIZER macro has the same effect as dynamically allocating pthread_cond_init() with null attributes. No error checking is done. Multiple threads must not simultaneously initialize or reinitialize the same condition variable. If a condition variable is reinitialized or is destroyed, the application must be sure that the condition variable is not in use. pthread_cond_init Return Valuespthread_cond_init() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value. EINVAL Description:The value specified by cattr is invalid. EBUSY Description:The condition variable is being used. EAGAIN Description:The necessary resources are not available. ENOMEM Description:Insufficient memory exists to initialize the condition variable. Blocking on a Condition VariableUse pthread_cond_wait(3C) to atomically release the mutex pointed to by mp and to cause the calling thread to block on the condition variable pointed to by cv. pthread_cond_wait Syntaxint pthread_cond_wait(pthread_cond_t *restrict cv,pthread_mutex_t *restrict mutex); #include <pthread.h> pthread_cond_t cv; pthread_mutex_t mp; int ret; /* wait on condition variable */ ret = pthread_cond_wait(&cv, & mp); The blocked thread can be awakened by a pthread_cond_signal() , a pthread_cond_broadcast(), or when interrupted by delivery of a signal. Any change in the value of a condition that is associated with the condition variable cannot be inferred by the return of pthread_cond_wait(). Such conditions must be reevaluated. The pthread_cond_wait() routine always returns with the mutex locked and owned by the calling thread, even when returning an error. This function blocks until the condition is signaled. The function atomically releases the associated mutex lock before blocking, and atomically acquires the mutex again before returning. In typical use, a condition expression is evaluated under the protection of a mutex lock. When the condition expression is false, the thread blocks on the condition variable. The condition variable is then signaled by another thread when the thread changes the condition value. The change causes at least one thread that is waiting on the condition variable to unblock and to reacquire the mutex. The condition that caused the wait must be retested before continuing execution from the point of the pthread_cond_wait(). The condition could change before an awakened thread reacquires the mutes and returns from pthread_cond_wait(). A waiting thread could be awakened spuriously. The recommended test method is to write the condition check as a while() loop that calls pthread_cond_wait(). pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order. Note – pthread_cond_wait() is a cancellation point. If a cancel is pending and the calling thread has cancellation enabled, the thread terminates and begins executing its cleanup handlers while continuing to hold the lock. pthread_cond_wait Return Valuespthread_cond_wait() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value. EINVAL Description:The value specified by cv or mp is invalid. Unblocking One ThreadUse pthread_cond_signal(3C) to unblock one thread that is blocked on the condition variable pointed to by cv. pthread_cond_signal Syntaxint pthread_cond_signal(pthread_cond_t *cv); #include <pthread.h> pthread_cond_t cv; int ret; /* one condition variable is signaled */ ret = pthread_cond_signal(&cv); Modify the associated condition under the protection of the same mutex used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait. The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order. When no threads are blocked on the condition variable, calling pthread_cond_signal() has no effect. Example 4–8 Using pthread_cond_wait() and pthread_cond_signal()pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
pthread_cond_signal Return Valuespthread_cond_signal() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value. EINVAL Description:cv points to an illegal address. Example 4–8 shows how to use pthread_cond_wait() and pthread_cond_signal(). Blocking Until a Specified TimeUse pthread_cond_timedwait(3C) as you would use pthread_cond_wait(), except that pthread_cond_timedwait() does not block past the time of day specified by abstime . pthread_cond_timedwait Syntaxint pthread_cond_timedwait(pthread_cond_t *restrict cv,
pthread_mutex_t *restrict mp,
const struct timespec *restrict abstime);
#include <pthread.h> #include <time.h> pthread_cond_t cv; pthread_mutex_t mp; timestruct_t abstime; int ret; /* wait on condition variable */ ret = pthread_cond_timedwait(&cv, & mp, &abstime); pthread_cond_timedwait() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_timedwait() is returning an error. The pthread_cond_timedwait() function blocks until the condition is signaled or until the time of day specified by the last argument has passed. Note – pthread_cond_timedwait() is also a cancellation point. Example 4–9 Timed Condition Waitpthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
clock_gettime(CLOCK_REALTIME, &to);
to.tv_sec += TIMEOUT;
while (cond == FALSE) {
err = pthread_cond_timedwait(&c, &m, &to);
if (err == ETIMEDOUT) {
/* timeout, do something */
break;
}
}
pthread_mutex_unlock(&m);
pthread_cond_timedwait Return Valuespthread_cond_timedwait() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value. EINVAL Description:cv, mp, or abstime points to an illegal address. EINVAL Description:Different mutexes were supplied for concurrent pthread_cond_timedwait() operations on the same condition variable. ETIMEDOUT Description:The time specified by abstime has passed. EPERM Description:The mutex was not owned by the current thread at the time of the call. The timeout is specified as a time of day so that the condition can be retested efficiently without recomputing the value, as shown in Example 4–9. Blocking For a Specified IntervalUse pthread_cond_reltimedwait_np(3C) as you would use pthread_cond_timedwait() with one exception. pthread_cond_reltimedwait_np() takes a relative time interval rather than an absolute future time of day as the value of its last argument. pthread_cond_reltimedwait_np Syntaxint pthread_cond_reltimedwait_np(pthread_cond_t *cv,
pthread_mutex_t *mp,
const struct timespec *reltime);
#include <pthread.h> #include <time.h> pthread_cond_t cv; pthread_mutex_t mp; timestruct_t reltime; int ret; /* wait on condition variable */ ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime); pthread_cond_reltimedwait_np() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_reltimedwait_np() is returning an error. The pthread_cond_reltimedwait_np() function blocks until the condition is signaled or until the time interval specified by the last argument has elapsed. Note – pthread_cond_reltimedwait_np() is also a cancellation point. pthread_cond_reltimedwait_np Return Valuespthread_cond_reltimedwait_np() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value. EINVAL Description:The value specified by reltime is invalid. ETIMEDOUT Description:The time interval specified by reltime has passed. Unblocking All ThreadsUse pthread_cond_broadcast(3C) to unblock all threads that are blocked on the condition variable pointed to by cv, specified by pthread_cond_wait(). pthread_cond_broadcast Syntaxint pthread_cond_broadcast(pthread_cond_t *cv); #include <pthread.h> pthread_cond_t cv; int ret; /* all condition variables are signaled */ ret = pthread_cond_broadcast(&cv); When no threads are blocked on the condition variable, pthread_cond_broadcast() has no effect. Since pthread_cond_broadcast() causes all threads blocked on the condition to contend again for the mutex lock, use pthread_cond_broadcast() with care. For example, use pthread_cond_broadcast() to allow threads to contend for varying resource amounts when resources are freed, as shown in Example 4–10. Example 4–10 Condition Variable Broadcastpthread_mutex_t rsrc_lock;
pthread_cond_t rsrc_add;
unsigned int resources;
get_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
while (resources < amount) {
pthread_cond_wait(&rsrc_add, &rsrc_lock);
}
resources -= amount;
pthread_mutex_unlock(&rsrc_lock);
}
add_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
resources += amount;
pthread_cond_broadcast(&rsrc_add);
pthread_mutex_unlock(&rsrc_lock);
}
Note that in add_resources() whether resources are updated first, or if pthread_cond_broadcast() is called first inside the mutex lock does not matter. Modify the associated condition under the protection of the same mutex that is used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait. pthread_cond_broadcast Return Valuespthread_cond_broadcast() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value. EINVAL Description:cv points to an illegal address. Destroying the Condition Variable StateUse pthread_cond_destroy(3C) to destroy any state that is associated with the condition variable pointed to by cv. pthread_cond_destroy Syntaxint pthread_cond_destroy(pthread_cond_t *cv); #include <pthread.h> pthread_cond_t cv; int ret; /* Condition variable is destroyed */ ret = pthread_cond_destroy(&cv); Note that the space for storing the condition variable is not freed. pthread_cond_destroy Return Valuespthread_cond_destroy() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value. EINVAL Description:The value specified by cv is invalid. Lost Wake-Up ProblemA call to pthread_cond_signal() or pthread_cond_broadcast() when the thread does not hold the mutex lock associated with the condition can lead to lost wake-up bugs. A lost wake-up occurs when all of the following conditions are in effect:
This can occur only if the condition being tested is modified without holding the mutex lock associated with the condition. As long as the condition being tested is modified only while holding the associated mutex, pthread_cond_signal() and pthread_cond_broadcast() can be called regardless of whether they are holding the associated mutex. Producer and Consumer ProblemThe producer and consumer problem is one of the small collection of standard, well-known problems in concurrent programming. A finite-size buffer and two classes of threads, producers and consumers, put items into the buffer (producers) and take items out of the buffer (consumers). A producer cannot put something in the buffer until the buffer has space available. A consumer cannot take something out of the buffer until the producer has written to the buffer. A condition variable represents a queue of threads that wait for some condition to be signaled. Example 4–11 has two such queues. One (less) queue for producers waits for a slot in the buffer. The other (more) queue for consumers waits for a buffer slot containing information. The example also has a mutex, as the data structure describing the buffer must be accessed by only one thread at a time. Example 4–11 Producer and Consumer Problem With Condition Variablestypedef struct {
char buf[BSIZE];
int occupied;
int nextin;
int nextout;
pthread_mutex_t mutex;
pthread_cond_t more;
pthread_cond_t less; }
buffer_t;
buffer_t buffer;
As Example 4–12 shows, the producer thread acquires the mutex protecting the buffer data structure. The producer thread then makes certain that space is available for the item produced. If space is not available, the producer thread calls pthread_cond_wait() . pthread_cond_wait() causes the producer thread to join the queue of threads that are waiting for the condition less to be signaled. less represents available room in the buffer. At the same time, as part of the call to pthread_cond_wait(), the thread releases its lock on the mutex. The waiting producer threads depend on consumer threads to signal when the condition is true, as shown in Example 4–12. When the condition is signaled, the first thread waiting on less is awakened. However, before the thread can return from pthread_cond_wait(), the thread must acquire the lock on the mutex again. Acquire the mutex to ensure that the thread again has mutually exclusive access to the buffer data structure. The thread then must check that available room in the buffer actually exists. If room is available, the thread writes into the next available slot. At the same time, consumer threads might be waiting for items to appear in the buffer. These threads are waiting on the condition variable more . A producer thread, having just deposited something in the buffer, calls pthread_cond_signal() to wake up the next waiting consumer. If no consumers are waiting, this call has no effect. Finally, the producer thread unlocks the mutex, allowing other threads to operate on the buffer data structure. Example 4–12 The Producer and Consumer Problem: the Producervoid producer(buffer_t *b, char item)
{
pthread_mutex_lock(&b->mutex);
while (b->occupied >= BSIZE)
pthread_cond_wait(&b->less, &b->mutex);
assert(b->occupied < BSIZE);
b->buf[b->nextin++] = item;
b->nextin %= BSIZE;
b->occupied++;
/* now: either b->occupied < BSIZE and b->nextin is the index
of the next empty slot in the buffer, or
b->occupied == BSIZE and b->nextin is the index of the
next (occupied) slot that will be emptied by a consumer
(such as b->nextin == b->nextout) */
pthread_cond_signal(&b->more);
pthread_mutex_unlock(&b->mutex);
}
Note the use of the assert() statement. Unless the code is compiled with NDEBUG defined, assert() does nothing when its argument evaluates to true (nonzero). The program aborts if the argument evaluates to false (zero). Such assertions are especially useful in multithreaded programs. assert() immediately points out runtime problems if the assertion fails. assert() has the additional effect of providing useful comments. The comment that begins /* now: either b->occupied ... could better be expressed as an assertion, but the statement is too complicated as a Boolean-valued expression and so is given in English. Both assertions and comments are examples of invariants. These invariants are logical statements that should not be falsified by the execution of the program with the following exception. The exception occurs during brief moments when a thread is modifying some of the program variables mentioned in the invariant. An assertion, of course, should be true whenever any thread executes the statement. The use of invariants is an extremely useful technique. Even if the invariants are not stated in the program text, think in terms of invariants when you analyze a program. The invariant in the producer code that is expressed as a comment is always true whenever a thread executes the code where the comment appears. If you move this comment to just after the mutex_unlock(), the comment does not necessarily remain true. If you move this comment to just after the assert() , the comment is still true. This invariant therefore expresses a property that is true at all times with the following exception. The exception occurs when either a producer or a consumer is changing the state of the buffer. While a thread is operating on the buffer under the protection of a mutex, the thread might temporarily falsify the invariant. However, once the thread is finished, the invariant should be true again. Example 4–13 shows the code for the consumer. The logic flow is symmetric with the logic flow of the producer. Example 4–13 The Producer and Consumer Problem: the Consumerchar 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);
}
Synchronization With SemaphoresA 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. Named and Unnamed SemaphoresPOSIX 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. Counting Semaphores OverviewConceptually, 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
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. Initializing a SemaphoreUse sem_init(3RT) to initialize the unnamed semaphore variable pointed to by sem to value amount. sem_init Syntaxint 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. Initializing Semaphores With Intraprocess ScopeWhen pshared is 0, the semaphore can be used by all the threads in this process only. #include <semaphore.h> sem_t sem; int ret; int count = 4; /* to be used within this process only */ ret = sem_init(&sem, 0, count); Initializing Semaphores With Interprocess ScopeWhen 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 Return Valuessem_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. Incrementing a SemaphoreUse sem_post(3RT) to atomically increment the semaphore pointed to by sem. sem_post Syntaxint 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 Return Valuessem_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. Blocking on a Semaphore CountUse 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. sem_wait Syntaxint sem_wait(sem_t *sem); #include <semaphore.h> sem_t sem; int ret; ret = sem_wait(&sem); /* wait for semaphore */ sem_wait Return Valuessem_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. Decrementing a Semaphore CountUse sem_trywait(3RT) to try to atomically decrement the count in the semaphore pointed to by sem when the count is greater than zero. sem_trywait Syntaxint 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 Return Valuessem_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. Destroying the Semaphore StateUse sem_destroy(3RT) to destroy any state that is associated with the unnamed semaphore pointed to by sem. sem_destroy Syntaxint 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 Return Valuessem_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. Producer and Consumer Problem Using SemaphoresThe 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. Example 4–14 Producer and Consumer Problem With Semaphorestypedef 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. Example 4–15 Producer and Consumer Problem: the Producervoid producer(buffer_t *b, char item) {
sem_wait(&b->empty);
sem_wait(&b->pmut);
b->buf[b->nextin] = item;
b->nextin++;
b->nextin %= BSIZE;
sem_post(&b->pmut);
sem_post(&b->occupied);
}
Example 4–16 Producer and Consumer Problem: the Consumerchar consumer(buffer_t *b) {
char item;
sem_wait(&b->occupied);
sem_wait(&b->cmut);
item = b->buf[b->nextout];
b->nextout++;
b->nextout %= BSIZE;
sem_post(&b->cmut);
sem_post(&b->empty);
return(item);
}
Read-Write Lock AttributesRead-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
Initializing a Read-Write Lock Attributepthread_rwlockattr_init(3C) initializes a read-write lock attributes object attr with the default value for all of the attributes defined by the implementation. pthread_rwlockattr_init Syntax#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. pthread_rwlockattr_init Return ValuesIf 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. Destroying a Read-Write Lock Attributepthread_rwlockattr_destroy(3C) destroys a read-write lock attributes object. pthread_rwlockattr_destroy Syntax#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. pthread_rwlockattr_destroy Return ValuesIf 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. Setting a Read-Write Lock Attributepthread_rwlockattr_setpshared(3C) sets the process-shared read-write lock attribute. pthread_rwlockattr_setpshared Syntax#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. pthread_rwlockattr_setpshared Return ValuesIf 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. Getting a Read-Write Lock Attributepthread_rwlockattr_getpshared(3C) gets the process-shared read-write lock attribute. pthread_rwlockattr_getpshared Syntax#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. pthread_rwlockattr_getpshared Return ValuesIf 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. Using Read-Write LocksAfter 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
Initializing a Read-Write LockUse pthread_rwlock_init(3C) to initialize the read-write lock referenced by rwlock with the attributes referenced by attr. pthread_rwlock_init Syntax#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. pthread_rwlock_init Return ValuesIf 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. Acquiring the Read Lock on Read-Write Lockpthread_rwlock_rdlock(3C) applies a read lock to the read-write lock referenced by rwlock. pthread_rwlock_rdlock Syntax#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. pthread_rwlock_rdlock Return ValuesIf 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. Acquiring a Read Lock on a Read-Write Lock Before a Specified Absolute TimeThe pthread_rwlock_timedrdlock(3C) function applies a read lock to the read-write lock referenced by rwlock as in the pthread_rwlock_rdlock() function. pthread_rwlock_timedrdlock Syntax#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. pthread_rwlock_timedrdlock Return ValuesIf 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. Acquiring a Non-Blocking Read Lock on a Read-Write Lockpthread_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. pthread_rwlock_tryrdlock Syntax#include <pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); pthread_rwlock_tryrdlock Return Valuespthread_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. Acquiring the Write Lock on a Read-Write Lockpthread_rwlock_wrlock(3C) applies a write lock to the read-write lock referenced by rwlock. pthread_rwlock_wrlock Syntax#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_wrlock Return Valuespthread_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. Acquiring a Non-blocking Write Lock on a Read-Write Lockpthread_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. pthread_rwlock_trywrlock Syntax#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. pthread_rwlock_trywrlock Return ValuesIf 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. Acquiring a Write Lock on a Read-Write Lock Before a Specified Absolute TimeThe 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. pthread_rwlock_timedwrlock Syntax#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. pthread_rwlock_timedwrlock ReturnsIf 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. Unlocking a Read-Write Lockpthread_rwlock_unlock(3C) releases a lock held on the read-write lock object referenced by rwlock. pthread_rwlock_unlock Syntax#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. pthread_rwlock_unlock Return ValuesIf successful, pthread_rwlock_unlock() returns zero. Otherwise, an error number is returned to indicate the error. Destroying a Read-Write Lockpthread_rwlock_destroy(3C) destroys the read-write lock object referenced by rwlock and releases any resources used by the lock. pthread_rwlock_destroy Syntax#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. pthread_rwlock_destroy Return ValuesIf 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. Using Barrier SynchronizationIn 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. Initializing a Synchronization BarrierUse pthread_barrier_init(3C) to allocate resources for a barrier and initialize its attributes. pthread_barrier_init() Syntaxint 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() Return Valuespthread_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. Waiting for Threads to Synchronize at a BarrierUse 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. pthread_barrier_wait() Syntaxint pthread_barrier_wait(pthread_barrier_t *barrier); #include <pthread.h> pthread_barrier_t barrier; int ret; ret = pthread_barrier_wait(&barrier); pthread_barrier_wait() Return ValuesWhen 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. Destroying a Synchronization BarrierWhen 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. pthread_barrier_destroy Syntaxint pthread_barrier_destroy(pthread_barrier_t *barrier); #include <pthread.h> pthread_barrier_t barrier; int ret; ret = pthread_barrier_destroy(&barrier); pthread_barrier_destroy Return Valuespthread_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. Initializing a Barrier Attributes ObjectThe 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. pthread_barrierattr_init() Syntaxint pthread_barrierattr_init(pthread_barrierattr_t *attr); #include <pthread.h> pthread_barrierattr_t attr; int ret; ret = pthread_barrierattr_init(&attr); pthread_barrierattr_init() Return Valuespthread_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. Setting a Barrier Process-Shared AttributeThe 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:
pthread_barrierattr_setpshared() Syntaxint pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared); pthread_barrierattr_setpshared() Return Valuespthread_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. Getting a Barrier Process-Shared AttributeThe 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. pthread_barrierattr_getpshared() Syntaxint pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr,
int *restrict pshared);
pthread_barrierattr_getpshared() Return Valuespthread_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. Destroying a Barrier Attributes ObjectThe 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. pthread_barrierattr_destroy() Syntax#include <pthread.h> int pthread_barrierattr_destroy(pthread_barrierattr_t *attr); pthread_barrierattr_destroy() Return Valuespthread_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. Synchronization Across Process BoundariesEach 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. Producer and Consumer Problem ExampleExample 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. Example 4–17 Synchronization Across Process Boundariesmain() {
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);
}
}
Comparing PrimitivesThe 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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||