When we multiple threads running they will invariably need to communicate with each
other in order synchronise their execution. This chapter describes the
synchronization types available with threads and discusses when and how to use
synchronization.

There are a few possible methods of synchronising threads:

Mutual Exclusion (Mutex) Locks

Condition Variables

Semaphores

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

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

Here are some example situations that require or can
profit from the use of synchronization:

When synchronization is the only way to
ensure consistency of shared data.

When threads in two or more processes can use a
single synchronization object jointly. Note that the synchronization object should be
initialized by only one of the cooperating processes, because reinitializing a
synchronization object sets it to the unlocked state.

When synchronization can ensure
the safety of mutable data.

When a process can map a file and have a thread in this
process get a record's lock. Once the lock is acquired, any other thread in any process
mapping the file that tries to acquire the lock is blocked until the lock is released.

Even when accessing a single primitive variable, such as an integer. On machines
where the integer is not aligned to the bus data width or is larger than the data
width, a single memory load can use more than one memory cycle. While this cannot
happen on the SPARC architectures, portable programs cannot rely on this.

Mutual exclusion locks (mutexes) are a comon method of serializing 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.

Mutex attributes may be associated with every thread. To change the default mutex
attributes, you can declare and initialize an mutex attribute object and then alter
specific values much like we have seen in the last chapter on more general POSIX
attributes. Often, the mutex attributes are set in one place at the beginning of the
application so they can be located quickly and modified easily.

After the attributes for a
mutex are configured, you initialize the mutex itself. Functions are available
to initialize or destroy, lock or unlock, or try to lock a mutex.

The function pthread_mutexattr_init() is used
to initialize attributes associated with this object to their default values. It is
prototyped by:

int pthread_mutexattr_init(pthread_mutexattr_t *mattr);

Storage
for each attribute object is allocated by the threads system during execution. mattr is an opaque type that contains a system-allocated attribute object. The
possible values of mattr's scope are PTHREAD_PROCESS_PRIVATE (the default) and
PTHREAD_PROCESS_SHARED.The
default value of the pshared attribute when this function is called is
PTHREAD_PROCESS_PRIVATE, which means that the initialized mutex can be used
within a process.

Before a mutex attribute object can be reinitialized, it must
first be destroyed by pthread_mutexattr_destroy() (see below). The
pthread_mutexattr_init() call returns a pointer to an opaque object. If the
object is not destroyed, a memory leak will result.
pthread_mutexattr_init() returns zero after completing successfully. Any other
returned value indicates that an error occurred.

The scope of a mutex
variable can be either process private (intraprocess) or system wide (interprocess).
The function pthread_mutexattr_setpshared() is used to set the scope of
a mutex atrribute and it is prototype as follows:

If
the mutex is created with the pshared (POSIX) attribute set to the
PTHREAD_PROCESS_SHARED state, and it exists in shared memory, it can be shared
among threads from more than one process. This is equivalent to the
USYNC_PROCESS flag in mutex_init() in Solaris
threads.
If the mutex pshared
attribute is set to PTHREAD_PROCESS_PRIVATE, only those threads created by the
same process can operate on the mutex. This is equivalent to the
USYNC_THREAD flag in mutex_init() in Solaris
threads.

pthread_mutexattr_setpshared() returns zero after completing successfully. Any
other returned value indicates that an error occurred.

Here, pthread_mutex_init() initializes the mutex pointed at by mp to its
default value if mattr is NULL, or to specify mutex attributes that have
already been set with pthread_mutexattr_init().

A mutex lock must not be reinitialized or destroyed while other
threads might be using it. Program failure will result if either action is not done
correctly. If a mutex is reinitialized or destroyed, the application must be sure the
mutex is not currently in use. pthread_mutex_init() returns zero after
completing successfully. Any other returned value indicates that an error occurred.

When the mutex is
initialized, it is in an unlocked state. The effect of mattr being NULL is
the same as passing the address of a default mutex attribute object, but without the
memory overhead. Statically defined mutexes can be initialized directly to have default
attributes with the macro PTHREAD_MUTEX_INITIALIZER.

The function pthread_mute_lock() is used to lock a mutex, it is prototyped by:

int pthread_mutex_lock(pthread_mutex_t *mp);

pthread_mute_lock() locks the mutex pointed to by mp. When the mutex is
already locked, the calling thread blocks and the mutex waits on a prioritized queue.
When pthread_mute_lock() returns, the mutex is locked and the calling thread
is the owner. pthread_mute_lock() returns zero after completing successfully.
Any other returned value indicates that an error occurred.

To unlock a mutex use the function pthread_mutex_unlock()
whose prototype is:

int pthread_mutex_unlock(pthread_mutex_t *mp);

Clearly, this function unlocks the mutex
pointed to by mp.

The mutex must be locked and the calling thread must be the one that last
locked the mutex (i.e. the owner). When any other threads are waiting for the
mutex to become available, the thread at the head of the queue is unblocked.
pthread_mutex_unlock() returns zero after completing successfully. Any other
returned value indicates that an error occurred.

The function pthread_mutex_trylock() to
attempt to lock the mutex and is prototyped by:

int pthread_mutex_trylock(pthread_mutex_t *mp);

This function attempts to lock the mutex pointed to by mp.
pthread_mutex_trylock() is a nonblocking version of pthread_mutex_lock().
When the mutex is already locked, this call returns with an error. Otherwise, the mutex
is locked and the calling thread is the owner. pthread_mutex_trylock()
returns zero after completing successfully. Any other returned value indicates that an
error occurred.

You may occasionally want to access two
resources at once. For instance, you are using one of the resources, and then discover
that the other resource is needed as well. However, there could be a problem
if two threads attempt to claim both resources but lock the associated mutexes in
different orders.

In this example, if the two threads lock mutexes 1 and 2
respectively, then a deadlock occurs when each attempts to lock the other mutex.

Thread 1

Thread 2

/* use resource 1 */

/* use resource 2 */

pthread_mutex_lock(&m1);

pthread_mutex_lock(&m2);

/* NOW use resources 2 + 1 */

/* NOW use resources 1 + 2 */

pthread_mutex_lock(&m2);

pthread_mutex_lock(&m1);

pthread_mutex_lock(&m1);

pthread_mutex_lock(&m2);

The best way to avoid this problem is to make sure that
whenever threads lock multiple mutexes, they do so in the same order. This technique is
known as lock hierarchies: order the mutexes by logically assigning numbers to them.
Also, honor the restriction that you cannot take a mutex that is assigned n when you
are holding any mutex assigned a number greater than n.

Note: The lock_lint tool can
detect the sort of deadlock problem shown in this example.

The best way to avoid such
deadlock problems is to use lock hierarchies. When locks are always taken in a
prescribed order, deadlock should not occur. However, this technique cannot always be
used :

sometimes you must take the mutexes in an order other than prescribed.

To prevent deadlock in such a situation, use pthread_mutex_trylock(). One
thread must release its mutexes when it discovers that deadlock would otherwise be
inevitable.

In the above example, thread 1 locks
mutexes in the prescribed order, but thread 2 takes them out of order. To make certain
that there is no deadlock, thread 2 has to take mutex 1 very carefully; if it were to
block waiting for the mutex to be released, it is likely to have just entered into a
deadlock with thread 1. To ensure this does not happen, thread 2 calls
pthread_mutex_trylock(), which takes the mutex if it is available. If it is not,
thread 2 returns immediately, reporting failure. At this point, thread 2 must release
mutex 2, so that thread 1 can lock it, and then release both mutex 1 and mutex 2.

To initialize a mutex use int mutex_init(mutex_t *mp, int type, void *arg)).
mutex_init() initializes the mutex pointed to by mp. The type can be
one of the following (note that arg is currently ignored).

USYNC_PROCESS

-- The
mutex can be used to synchronize threads in this and other processes.

USYNC_THREAD

-- The
mutex can be used to synchronize threads in this process, only.

Mutexes can also be
initialized by allocation in zeroed memory, in which case a type of USYNC_THREAD
is assumed. Multiple threads must not
initialize the same mutex simultaneously. A mutex lock must not be reinitialized while
other threads might be using it.

The function int mutex_destroy (mutex_t *mp)
destroys any state associated with the mutex pointed to by mp. Note
that the space for storing the mutex is not freed.

To acquire a mutex lock use the function mutex_lock(mutex_t *mp)
which locks the mutex pointed to by mp. When the mutex is already locked,
the calling thread blocks until the mutex becomes available (blocked threads wait on a
prioritized queue).

To release a mutex use mutex_unlock(mutex_t *mp) which unlocks the
mutex pointed to by mp. The mutex must be locked and the calling thread must be the one
that last locked the mutex (the owner).

To try to acquire a mutex use mutex_trylock(mutex_t *mp) to attempt to
lock the mutex pointed to by mp. This function is a nonblocking version of
mutex_lock()

Condition variables can be usedto atomically block
threads until a particular condition is true. Condition variables are always used
in conjunction with mutex locks:

With a condition variable, a thread can atomically
block until a condition is satisfied.

The condition is tested under the protection of a
mutual exclusion lock (mutex).

When the condition is false, a thread usually blocks on a
condition variable and atomically releases the mutex waiting for the condition to
change.

When another thread changes the condition, it can signal the associated
condition variable to cause one or more waiting threads to wake up, acquire the mutex
again, and reevaluate the condition.

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

The
scheduling policy determines how blocking threads are awakened. For the default
SCHED_OTHER, threads are awakened in priority order. The attributes for condition
variables must be set and initialized before the condition variables can be used.

As with mutex locks, The condiotion variable attributes must be initialised and set (or set
to NULL) before an actual condition variable may be initialise (with
appropriat attributes) and then used.

The function pthread_condattr_init() initializes attributes associated with
this object to their default values. It is prototyped by:

int pthread_condattr_init(pthread_condattr_t *cattr);

Storage for each attribute object, cattr, is allocated by
the threads system during execution. 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. The default value
of the pshared attribute when this function is called is
PTHREAD_PROCESS_PRIVATE, which means that the initialized condition variable can be
used within a process.

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

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

The function pthread_condattr_destroy() removes storage and renders
the attribute object invalid, it is prototyped by:

int pthread_condattr_destroy(pthread_condattr_t *cattr);

pthread_condattr_destroy() returns zero
after completing successfully and destroying the condition variable pointed to by
cattr. Any other returned value indicates that an error occurred. If the following
condition occurs, the function fails and returns the corresponding value.

The scope of a condition variable can be either process
private (intraprocess) or system wide (interprocess), as with mutex locks. If the
condition variable is created with the pshared attribute set to the
PTHREAD_PROCESS_SHARED state, and it exists in shared memory, it can be shared
among threads from more than one process. This is equivalent to the USYNC_PROCESS
flag in mutex_init() in the original Solaris threads. If the mutex pshared
attribute is set to PTHREAD_PROCESS_PRIVATE (default value), only those threads
created by the same process can operate on the mutex. Using
PTHREAD_PROCESS_PRIVATE results in the same behavior as with the USYNC_THREAD
flag in the original Solaris threads cond_init() call, which is that of a local
condition variable. PTHREAD_PROCESS_SHARED is equivalent to a global condition
variable.

The function pthread_condattr_setpshared() is used to set the scope of a
condition variable, it is prototyped by:

The condition variable which is initialized is pointed at by cv and is set to its
default value if cattr is NULL, or to specific cattr condition
variable attributes that are already set with pthread_condattr_init().
The
effect of cattr being NULL is the same as passing the address of a default
condition variable attribute object, but without the memory overhead.

Statically-defined condition variables can be initialized directly to have default
attributes with the macro PTHREAD_COND_INITIALIZER. This has the same effect as
dynamically allocating pthread_cond_init() with null attributes. No error
checking is done. Multiple threads must not simultaneously initialize or reinitialize
the same condition variable. If a condition variable is reinitialized or destroyed, the
application must be sure the condition variable is not in use.

pthread_cond_init() returns zero after completing successfully. Any other
returned value indicates that an error occurred.

The function pthread_cond_wait() is used to atomically release a mutex and to
cause the calling thread to block on the condition variable. It is protoyped by:

int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);

The mutex that is released is pointed
to by mutex and the condition variable
pointed to by cv is blocked.

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

The blocked thread
can be awakened by a pthread_cond_signal(), a pthread_cond_broadcast(),
or when interrupted by delivery of a signal. Any change in the value of a condition
associated with the condition variable cannot be inferred by the return of
pthread_cond_wait(), and any such condition must be reevaluated. The
pthread_cond_wait() routine always returns with the mutex locked and owned by
the calling thread, even when returning an error. This function blocks until the
condition is signaled. It atomically releases the associated mutex lock before
blocking, and atomically acquires it again before returning. In typical use, a
condition expression is evaluated under the protection of a mutex lock. When the
condition expression is false, the thread blocks on the condition variable. The
condition variable is then signaled by another thread when it changes the condition
value. This causes one or all of the threads waiting on the condition to unblock and to
try to acquire the mutex lock again. Because the condition can change before an
awakened thread returns from pthread_cond_wait(), the condition that
caused the wait must be retested before the mutex lock is acquired.

The recommended
test method is to write the condition check as a while loop that calls
pthread_cond_wait(), as follows:

No specific
order of acquisition is guaranteed when more than one thread blocks on the condition
variable. Note also that 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.

To unblock a specific thread use
pthread_cond_signal() which is prototyped by:

int pthread_cond_signal(pthread_cond_t *cv);

This unblocks one thread that is blocked on the condition
variable pointed to by cv.
pthread_cond_signal() returns zero after completing successfully. Any other returned
value indicates that an error occurred.

You should always call pthread_cond_signal() under the protection of the
same mutex used with the condition variable being signaled. Otherwise, the condition
variable could be signaled between the test of the associated condition and blocking in
pthread_cond_wait(), which can cause an infinite wait. The scheduling policy
determines the order in which blocked threads are awakened. For SCHED_OTHER,
threads are awakened in priority order. When no threads are blocked on the condition
variable, then calling pthread_cond_signal()l has no effect.

The folloowing code fragment illustrates how to avoid an infinite problem
described above:

pthread_cond_timedwait() is used in a similar manner to
pthread_cond_wait(): pthread_cond_timedwait() blocks
until the condition is signaled or until the time of day, specified by abstime,
has passed. pthread_cond_timedwait() always returns with the mutex, mp,
locked and owned by the calling thread, even when it is returning an error.
pthread_cond_timedwait() is also a cancellation point.

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

All threads may be unblocked in one function: pthread_cond_broadcast().
This function is prototyped as follows:

int pthread_cond_broadcast(pthread_cond_t *cv);

pthread_cond_broadcast() unblocks
all threads that are blocked on the condition variable pointed to by cv, specified
by pthread_cond_wait(). When no threads are blocked on the condition variable,
pthread_cond_broadcast() has no effect.

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

Since
pthread_cond_broadcast() causes all threads blocked on the condition to contend
again for the mutex lock, use carefully. For example, use
pthread_cond_broadcast() to allow threads to contend for varying resource amounts
when resources are freed:

Note: that in
add_resources it does not matter whether resources is updated first or if
pthread_cond_broadcast() is called first inside the mutex lock. Call
pthread_cond_broadcast() under the protection of the same mutex that is used
with the condition variable being signaled. Otherwise, the condition variable could be
signaled between the test of the associated condition and blocking in
pthread_cond_wait(), which can cause an infinite wait.

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

Similar
condition variables exist in Solaris. The functions are prototyped in
<thread.h>.

To initialize a condition variable use
int cond_init(cond_t *cv, int type, int arg) which initializes the condition
variable pointed to by cv. The type can be one of USYNC_PROCESS or
USYNC_THREAD (See Solaris mutex (Section 30.1.9 for more details).
Note that arg is currently ignored.

Condition variables
can also be initialized by allocation in zeroed memory, in which case a type of
USYNC_THREAD is assumed. Multiple threads must not initialize the same condition
variable simultaneously. A condition variable must not be reinitialized while other
threads might be using it.

To destroy a condition variable use int cond_destroy(cond_t *cv)
which destroys a state associated with the condition variable pointed to by
cv. The space for storing the condition variable is not freed.

To wait for a
condition use int cond_wait(cond_t *cv, mutex_t *mp) which atomically releases
the mutex pointed to by mp and to cause the calling thread to block on the
condition variable pointed to by cv.

The blocked thread can be awakened by cond_signal(cond_t *cv),
cond_broadcast(cond_t *cv), or when interrupted by delivery of a signal or a
fork. Use cond_signal() to unblock one thread that is blocked on the condition
variable pointed to by cv. Call this function under protection of the same mutex
used with the condition variable being signaled. Otherwise, the condition could be
signaled between its test and cond_wait(), causing an infinite wait. Use
cond_broadcast() to unblock all threads that are blocked on the condition variable
pointed to by cv. When no threads are blocked on the condition variable then
cond_broadcast() has no effect.

Finally, to wait until the condition is signaled or for
an absolute time use int cond_timedwait(cond_t *cv,
mutex_t *mp, timestruct_t abstime) Use cond_timedwait() as you would use
cond_wait(), except that cond_timedwait() does not block past the time of
day specified by abstime. cond_timedwait() always returns with the mutex locked
and owned by the calling thread even when returning an error.

To initialize the function int sema_init(sema_t *sp, unsigned int count,
int type, void *arg) is used. sema. type can be one of the following
):

USYNC_PROCESS

-- The semaphore can be used to
synchronize threads in this process and other processes. Only one process should
initialize the semaphore.

USYNC_THREAD

-- The semaphore can be used to
synchronize threads in this process.

arg is currently unused.

Multiple threads must not
initialize the same semaphore simultaneously. A semaphore must not be
reinitialized while other threads may be using it.

To increment a Semaphore use the function int sema_post(sema_t *sp).
sema_post atomically increments the semaphore pointed to by sp. When any threads
are blocked on the semaphore, one is unblocked.

To block on a Semaphore use int sema_wait(sema_t *sp). sema_wait() to
block the calling thread until the count in the semaphore pointed to by sp becomes
greater than zero, then atomically decrement it.

To decrement a Semaphore count use int sema_trywait(sema_t *sp).
sema_trywait() atomically decrements the count in the semaphore pointed to by
sp when the count is greater than zero. This function is a nonblocking version of
sema_wait().

To destroy the Semaphore state call the function
sema_destroy(sema_t *sp). sema_destroy() to
destroy any state associated with the semaphore pointed to by sp. The space for
storing the semaphore is not freed.