[ Next Article | Previous Article | Book Contents | Library Home | Legal | Search ]
General Programming Concepts: Writing and Debugging Programs

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. In the current version of AIX, the mutex attributes are not used. The mutex attributes object can therefore be ignored when creating a mutex.

Read the following to learn more about using mutexes:

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.

Mutex Attributes Object Creation and Destruction

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 free storage dynamically allocated by the pthread_mutexattr_init subroutine, depending on the implementation of the threads library.

In the following example, a mutex attributes object is created and initialized with default values, then used and finally destroyed:

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 two mutex creations. When the mutexes are created, the attributes object can be destroyed without affecting the mutexes created with it.

Mutex Attributes

In AIX, no mutex attribute is defined. They depend on POSIX options that are not implemented in AIX . However, the following attributes may be defined on other systems:

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.
Prioceiling Specifies the priority ceiling of a mutex. This attribute depends on the priority protection POSIX option.
Process-shared Specifies the process sharing of a mutex. This attribute depends on the process sharing POSIX option.

The default values for these attributes are sufficient for most simple cases. See Synchronization Scheduling for more information about the protocol and prioceiling attributes; see Advanced Attributes for more information about the process-shared attribute.

Creating and Destroying Mutexes

A mutex is created by calling the pthread_mutex_init subroutine. You may specify a mutex attributes object. If you specify a NULL pointer, the mutex will have the default attributes. Thus, the code fragment:

pthread_mutex_t mutex;
pthread_mutex_attr_t attr;
...
pthread_mutexattr_init(&attr);
pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);

is equivalent to:

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 may be a pointer or another data type.

A mutex must be created once. Calling the pthread_mutex_init subroutine more than once with the same mutex parameter (for example, in two threads concurrently executing the same code) should be avoided. The second call will fail, returning an EBUSY error code. Ensuring the uniqueness of a mutex creation can be done in three ways:

Once the mutex is no longer needed, it should be destroyed by calling the pthread_mutex_destroy subroutine. This subroutine may reclaim any storage allocated by the pthread_mutex_init subroutine. After having destroyed a mutex, the same pthread_mutex_t variable can be reused for creating another mutex. For example, the following code fragment is legal, although not very realistic:

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.

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:

The pthread_mutex_trylock subroutine acts like the pthread_mutex_lock subroutine without blocking the calling thread:

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:

Because locking does not provide a cancellation point, a thread blocked while waiting for a mutex cannot be canceled. Therefore, it is recommended to use mutexes only for short periods of time, like protecting data from concurrent access.

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. Making Complex Synchronization Objects provides more information about implementing long locks and writer-priority readers/writers locks with mutexes.

Mutex Usage Example

Mutexes can be used to protect data from concurrent access. For example, a database application may create several threads to handle several requests concurrently. The database itself is protected by a mutex, called db_mutex.

/* 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

In AIX, mutexes cannot be re-locked by the same thread. This may not be the case on other systems. To enhance portability of your programs, assume that the following code fragment may produce a deadlock:

pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);

This kind of deadlock may occur when locking a mutex and then calling a routine that will itself lock the same mutex. For example:

pthread_mutex_t mutex;
struct {
        int a;
        int b;
        int c;
} A;
f()
{
        pthread_mutex_lock(&mutex);      /* call 1 */
        A.a++;
        g();
        A.c = 0;
        pthread_mutex_unlock(&mutex);
}
g()
{
        pthread_mutex_lock(&mutex);      /* call 2 */
        A.b += A.a;
        pthread_mutex_unlock(&mutex);    /* call 3 */
}

On some non-AIX systems, calling the f subroutine would produce a deadlock; call 2 would block the thread, because call 1 already locked the mutex. In AIX, this code fragment would still not have the expected behavior. Call 2 would be unsuccessful, but call 3 would succeed. Thus, when returning for the g subroutine, the mutex would already be unlocked and the A variable would no longer be protected; when returning from the f routine, the A.c variable may not contain zero.

To avoid this kind of deadlock or data inconsistency, you should use either one of the following schemes:

Deadlocks may also occur when locking mutexes in reverse order. For example, the following code fragment may 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);

To avoid these kinds of deadlocks, you should ensure that successive mutexes are always locked in the same order.

Related Information

Thread Programming Concepts.

Synchronization Overview.

Using Condition Variables.

Joining Threads.

List of Synchronization Subroutines.

Threads Library Options.


[ Next Article | Previous Article | Book Contents | Library Home | Legal | Search ]