After the attributes for a mutex are configured, you initialize the mutex itself. The following functions are used to initialize or destroy, lock or unlock, or try to lock a mutex. Table 4-3 lists the functions discussed in this chapter 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 threads are waiting for a mutex, the order of acquisition is undefined. When there is contention, the default behavior is to unblock threads in priority order.
Use pthread_mutex_init() to initialize the mutex pointed at by mp to its default value (mattr is NULL), or to specify mutex attributes that have already been set with pthread_mutexattr_init().
Prototype: int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr);
#include <pthread.h> pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; pthread_mutexattr_t mattr; int ret; /* initialize a mutex to its default value */ ret = pthread_mutex_init(&mp, NULL); /* initialize a mutex */ ret = pthread_mutex_init(&mp, &mattr);
When the mutex is initialized, it is in an unlocked state.
The effect of mattr being NULL is the same as passing the address of a default mutex attribute object, but without the memory overhead.
Statically defined mutexes can be initialized directly to have default attributes with the macro PTHREAD_MUTEX_INITIALIZER.
A mutex lock must not be reinitialized or destroyed while other threads might be using it. Program failure will result if either action is not done correctly. If a mutex is reinitialized or destroyed, the application must be sure the mutex is not currently in use.
pthread_mutex_init() 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.
Prototype: int pthread_mutex_lock(pthread_mutex_t *mp);
#include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_ mutex_lock(&mp); /* acquire the mutex */
Use pthread_mutex_lock() to lock 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_mutex_lock() returns, the mutex is locked and the calling thread is the owner.
pthread_mutex_lock() 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.
Use pthread_mutex_unlock() to unlock the mutex pointed to by mp.
Prototype: int pthread_mutex_unlock(pthread_mutex_t *mp);
#include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_ mutex_unlock(&mp); /* release the mutex */
The mutex must be locked and the calling thread must be the one that last locked the mutex (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. When any of the following conditions occur, the function fails and returns the corresponding value.
Use pthread_mutex_trylock() to attempt to lock the mutex pointed to by mp.
Prototype: int pthread_mutex_trylock(pthread_mutex_t *mp);
#include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_ mutex_trylock(&mp); /* try to lock the mutex */
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. When any of the following conditions occur, the function fails and returns the corresponding value.
Use pthread_mutex_destroy() to destroy any state associated with the mutex pointed to by mp.
Prototype: int pthread_mutex_destroy(pthread_mutex_t *mp);
#include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */
Note that the space for storing the mutex is not freed.
pthread_mutex_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.
Here are some code fragments showing mutex locking.
#include <pthread.h> pthread_mutex_t count_mutex; long long count; void increment_count() { pthread_mutex_lock(&count_mutex); count = count + 1; pthread_mutex_unlock(&count_mutex); } long long get_count() { long long c; pthread_mutex_lock(&count_mutex); c = count; pthread_mutex_unlock(&count_mutex); return (c); }
The two functions in Example 4-1 use the mutex lock for different purposes. The increment_count() function uses the mutex lock simply to ensure an atomic update of the shared variable. The get_count() function uses the mutex lock to guarantee that the 64-bit quantity count is read atomically. On a 32-bit architecture, a long
long
is really two 32-bit quantities.
Reading an integer value is an atomic operation because integer is the common word size on most machines.
You will occasionally want to access two resources at once. Perhaps you are using one of the resources, and then discover that the other resource is needed as well. As shown in Failed Cross Reference Format, 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 |
---|---|
pthread_mutex_lock(&m1);
/* use resource 1 */
pthread_mutex_lock(&m2);
/* use resources1 and 2 */
pthread_mutex_unlock(&m2); pthread_mutex_unlock(&m1); |
pthread_mutex_lock(&m2);
/* use resource 2 */
pthread_mutex_lock(&m1);
/* use resources 1 and 2 */
pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2); |
The best way to avoid this problem is to make sure that whenever threads lock multiple mutexes, they do so in the same order. 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.
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.
Failed Cross Reference Format shows how this is done.
Thread 1 |
Thread 2 |
---|---|
pthread_mutex_lock(&m1); pthread_mutex_lock(&m2);
/* no processing */
pthread_mutex_unlock(&m2); pthread_mutex_unlock(&m1); |
for (; ;) { pthread_mutex_lock(&m2);
if(pthread_mutex_trylock(&m1)==0) /* got it! */ break; /* didn't get it */ pthread_mutex_unlock(&m2); } /* get locks; no processing */ pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2); |
In this 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.
Example 4-4 and Example 4-5 show how to take three locks at once, but prevent deadlock by taking the locks in a prescribed order.
typedef struct node1 { int value; struct node1 *link; pthread_mutex_t lock; } node1_t; node1_t ListHead;
This example uses a singly-linked list structure with each node containing a mutex. To remove a node from the list, first search the list starting at ListHead (which itself is never removed) until the desired node is found.
To protect this search from the effects of concurrent deletions, lock each node before any of its contents are accessed. Because all searches start at ListHead, there is never a deadlock because the locks are always taken in list order.
When the desired node is found, lock both the node and its predecessor since the change involves both nodes. Because the predecessor's lock is always taken first, you are again protected from deadlock. Example 4-5 shows the C code to remove an item from a singly linked list.
node1_t *delete(int value) { node1_t *prev, *current; prev = &ListHead; pthread_mutex_lock(&prev->lock); while ((current = prev->link) != NULL) { pthread_mutex_lock(¤t->lock); if (current->value == value) { prev->link = current->link; pthread_mutex_unlock(¤t->lock); pthread_mutex_unlock(&prev->lock); current->link = NULL; return(current); } pthread_mutex_unlock(&prev->lock); prev = current; } pthread_mutex_unlock(&prev->lock); return(NULL); }
Example 4-6 modifies the previous list structure by converting it into a circular list. There is no longer a distinguished head node; now a thread might be associated with a particular node and might perform operations on that node and its neighbor. Note that lock hierarchies do not work easily here because the obvious hierarchy (following the links) is circular.
typedef struct node2 { int value; struct node2 *link; pthread_mutex_t lock; } node2_t;
Here is the C code that acquires the locks on two nodes and performs an operation involving both of them.