Using mutexes
A mutex is a mutual exclusion lock. Only one thread can hold the lock.
Mutexes are used to protect data or other resources from concurrent access. A mutex has attributes, which specify the characteristics of the mutex.
Mutex attributes object
Like threads, mutexes are created with the help of an attributes object. The mutex attributes object is an abstract object, containing several attributes, depending on the implementation of POSIX options. It is accessed through a variable of type pthread_mutexattr_t. In AIX®, the pthread_mutexattr_t data type is a pointer; on other systems, it may be a structure or another data type.
Creating and destroying the mutex attributes object
The mutex attributes object is initialized to default values by the pthread_mutexattr_init subroutine. The attributes are handled by subroutines. The thread attributes object is destroyed by the pthread_mutexattr_destroy subroutine. This subroutine may release storage dynamically allocated by the pthread_mutexattr_init subroutine, depending on the implementation of the threads library.
pthread_mutexattr_t attributes;
/* the attributes object is created */
...
if (!pthread_mutexattr_init(&attributes)) {
/* the attributes object is initialized */
...
/* using the attributes object */
...
pthread_mutexattr_destroy(&attributes);
/* the attributes object is destroyed */
}
The same attributes object can be used to create several mutexes. It can also be modified between mutex creations. When the mutexes are created, the attributes object can be destroyed without affecting the mutexes created with it.
Mutex attributes
The following mutex attributes are defined:
Attribute | Description |
---|---|
Protocol | Specifies the protocol used to prevent priority inversions for a mutex. This attribute depends on either the priority inheritance or the priority protection POSIX option. |
Process-shared | Specifies the process sharing of a mutex. This attribute depends on the process sharing POSIX option. |
For more information on these attributes, see Threads Library Options and Synchronization Scheduling.
Creating and destroying mutexes
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
...
pthread_mutexattr_init(&attr);
pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_t mutex;
...
pthread_mutex_init(&mutex, NULL);
The ID of the created mutex is returned to the calling thread through the mutex parameter. The mutex ID is an opaque object; its type is pthread_mutex_t. In AIX, the pthread_mutex_t data type is a structure; on other systems, it might be a pointer or another data type.
A mutex must be created once. However, avoid calling the pthread_mutex_init subroutine more than once with the same mutex parameter (for example, in two threads concurrently executing the same code). Ensuring the uniqueness of a mutex creation can be done in the following ways:
- Calling the pthread_mutex_init subroutine prior to the creation of other threads that will use this mutex; for example, in the initial thread.
- Calling the pthread_mutex_init subroutine within a one time initialization routine. For more information, see One-Time Initializations.
- Using a static mutex initialized by the PTHREAD_MUTEX_INITIALIZER static initialization macro; the mutex will have default attributes.
pthread_mutex_t mutex;
...
for (i = 0; i < 10; i++) {
/* creates a mutex */
pthread_mutex_init(&mutex, NULL);
/* uses the mutex */
/* destroys the mutex */
pthread_mutex_destroy(&mutex);
}
Like any system resource that can be shared among threads, a mutex allocated on a thread's stack must be destroyed before the thread is terminated. The threads library maintains a linked list of mutexes. Thus, if the stack where a mutex is allocated is freed, the list will be corrupted.
Types of mutexes
- PTHREAD_MUTEX_DEFAULT or PTHREAD_MUTEX_NORMAL
- Results in a deadlock if the same pthread tries to lock it a second time using the pthread_mutex_lock subroutine without first unlocking it. This is the default type.
- PTHREAD_MUTEX_ERRORCHECK
- Avoids deadlocks by returning a non-zero value if the same thread attempts to lock the same mutex more than once without first unlocking the mutex.
- PTHREAD_MUTEX_RECURSIVE
- Allows the same pthread to recursively lock the mutex using the pthread_mutex_lock subroutine without resulting in a deadlock or getting a non-zero return value from pthread_mutex_lock. The same pthread has to call the pthread_mutex_unlock subroutine the same number of times as it called pthread_mutex_lock subroutine in order to unlock the mutex for other pthreads to use.
When a mutex attribute is first created, it has a default type of PTHREAD_MUTEX_NORMAL. After creating the mutex, the type can be changed using the pthread_mutexattr_settype API library call.
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex, &attr);
struct {
int a;
int b;
int c;
} A;
f()
{
pthread_mutex_lock(&mutex);
A.a++;
g();
A.c = 0;
pthread_mutex_unlock(&mutex);
}
g()
{
pthread_mutex_lock(&mutex);
A.b += A.a;
pthread_mutex_unlock(&mutex);
}
Locking and unlocking mutexes
A mutex is a simple lock, having two states: locked and unlocked. When it is created, a mutex is unlocked. The pthread_mutex_lock subroutine locks the specified mutex under the following conditions:
- If the mutex is unlocked, the subroutine locks it.
- If the mutex is already locked by another thread, the subroutine blocks the calling thread until the mutex is unlocked.
- If the mutex is already locked by the calling thread, the subroutine might block forever or return an error depending on the type of mutex.
The pthread_mutex_trylock subroutine acts like the pthread_mutex_lock subroutine without blocking the calling thread under the following conditions:
- If the mutex is unlocked, the subroutine locks it.
- If the mutex is already locked by any thread, the subroutine returns an error.
The thread that locked a mutex is often called the owner of the mutex.
The pthread_mutex_unlock subroutine resets the specified mutex to the unlocked state if it is owned by the calling mutex under the following conditions:
- If the mutex was already unlocked, the subroutine returns an error.
- If the mutex was owned by the calling thread, the subroutine unlocks the mutex.
- If the mutex was owned by another thread, the subroutine might return an error or unlock the mutex depending on the type of mutex. Unlocking the mutex is not recommended because mutexes are usually locked and unlocked by the same pthread.
Because locking does not provide a cancelation point, a thread blocked while waiting for a mutex cannot be canceled. Therefore, it is recommended that you use mutexes only for short periods of time, as in instances where you are protecting data from concurrent access. For more information, see Cancelation Points and Canceling a Thread.
Protecting data with mutexes
Mutexes are intended to serve either as a low-level primitive from which other thread synchronization functions can be built or as a data protection lock. For more information about implementing long locks and writer-priority readers/writers locks see Using mutexes.
Mutex usage example
/* the initial thread */
pthread_mutex_t mutex;
int i;
...
pthread_mutex_init(&mutex, NULL); /* creates the mutex */
for (i = 0; i < num_req; i++) /* loop to create threads */
pthread_create(th + i, NULL, rtn, &mutex);
... /* waits end of session */
pthread_mutex_destroy(&mutex); /* destroys the mutex */
...
/* the request handling thread */
... /* waits for a request */
pthread_mutex_lock(&db_mutex); /* locks the database */
... /* handles the request */
pthread_mutex_unlock(&db_mutex); /* unlocks the database */
...
The initial thread creates the mutex and all the request-handling threads. The mutex is passed to the thread using the parameter of the thread's entry point routine. In a real program, the address of the mutex may be a field of a more complex data structure passed to the created thread.
Avoiding Deadlocks
- A mutex created with the default type, PTHREAD_MUTEX_NORMAL, cannot be relocked by the same pthread without resulting in a deadlock.
- An application can deadlock when locking mutexes in reverse order.
For example, the following code fragment can produce a deadlock between
threads A and B.
/* Thread A */ pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2); /* Thread B */ pthread_mutex_lock(&mutex2); pthread_mutex_lock(&mutex1);
- An application can deadlock in what is called resource deadlock.
For example:
This application has two threads,struct { pthread_mutex_t mutex; char *buf; } A; struct { pthread_mutex_t mutex; char *buf; } B; struct { pthread_mutex_t mutex; char *buf; } C; use_all_buffers() { pthread_mutex_lock(&A.mutex); /* use buffer A */ pthread_mutex_lock(&B.mutex); /* use buffers B */ pthread_mutex_lock(&C.mutex); /* use buffer C */ /* All done */ pthread_mutex_unlock(&C.mutex); pthread_mutex_unlock(&B.mutex); pthread_mutex_unlock(&A.mutex); } use_buffer_a() { pthread_mutex_lock(&A.mutex); /* use buffer A */ pthread_mutex_unlock(&A.mutex); } functionB() { pthread_mutex_lock(&B.mutex); /* use buffer B */ if (..some condition) { use_buffer_a(); } pthread_mutex_unlock(&B.mutex); } /* Thread A */ use_all_buffers(); /* Thread B */ functionB();
thread A
andthread B
.Thread B
starts to run first, thenthread A
starts shortly thereafter. Ifthread A
executes use_all_buffers() and successfully locks A.mutex, it will then block when it tries to lock B.mutex, becausethread B
has already locked it. Whilethread B
executes functionB andsome_condition
occurs whilethread A
is blocked,thread B
will now also block trying to acquire A.mutex, which is already locked bythread A
. This results in a deadlock.The solution to this deadlock is for each thread to acquire all the resource locks that it needs before using the resources. If it cannot acquire the locks, it must release them and start again.
Mutexes and race conditions
Mutual exclusion locks (mutexes) can prevent data inconsistencies due to race conditions. A race condition often occurs when two or more threads must perform operations on the same memory area, but the results of computations depends on the order in which these operations are performed.
move X, REG
inc REG
move REG, X
If both threads in the previous example are executed concurrently on two CPUs, or if the scheduling makes the threads alternatively execute on each instruction, the following steps may occur:
- Thread A executes the first instruction and puts X, which
is 1, into the thread A register. Then thread B executes and puts X,
which is 1, into the thread B register. The following example illustrates
the resulting registers and the contents of memory X.
Thread A Register = 1 Thread B Register = 1 Memory X = 1
- Thread A executes the second instruction and increments the content
of its register to 2. Then thread B increments its register to 2.
Nothing is moved to memory X, so memory X stays the
same. The following example illustrates the resulting registers and
the contents of memory X.
Thread A Register = 2 Thread B Register = 2 Memory X = 1
- Thread A moves the content of its register, which is now 2, into
memory X. Then thread B moves the content of its register,
which is also 2, into memory X, overwriting thread A's value.
The following example illustrates the resulting registers and the
contents of memory X.
Thread A Register = 2 Thread B Register = 2 Memory X = 2
In most cases, thread A and thread B execute the three instructions one after the other, and the result would be 3, as expected. Race conditions are usually difficult to discover, because they occur intermittently.
To avoid this race condition, each thread should lock the data before accessing the counter and updating memory X. For example, if thread A takes a lock and updates the counter, it leaves memory X with a value of 2. After thread A releases the lock, thread B takes the lock and updates the counter, taking 2 as its initial value for X and increment it to 3, the expected result.