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.
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.
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 mutex creations. When the mutexes are created, the attributes object can be destroyed without affecting the mutexes created with it.
The following mutex attributes are defined:
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 (Threads Library Options). |
The following mutex attributes are not supported:
Prioceiling | Specifies the priority ceiling of a mutex. This attribute depends on the priority protection POSIX option (Threads Library Options). |
Process-shared | Specifies the process sharing of a mutex. This attribute depends on the process sharing POSIX option (Threads Library Options). |
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.
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 might 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. 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.
There are 3 types of mutex:
The type of mutex determines how the mutex behaves when it is operated on.
The default type, PTHREAD_MUTEX_NORMAL or PTHREAD_MUTEX_DEFAULT, 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.
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 did pthread_mutex_lock subroutine in order to unlock the mutex for other pthreads to use.
When a mutex 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.
The following is an example of creating and using a recursive mutex type:
pthread_mutex_attr_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); }
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. For more information, see Cancellation Points and Canceling a Thread.
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.
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.
There are a number of ways that a multi-threaded application can deadlock. Following are some examples.
/* Thread A */ pthread_mutex_lock(&mutex1); pthread_mutex_lock(&mutex2); /* Thread B */ pthread_mutex_lock(&mutex2); pthread_mutex_lock(&mutex1);
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_b() { pthread_mutex_lock(&B.mutex); /* use buffer B */ pthread_mutex_unlock(&B.mutex); } functionB() { pthread_mutex_lock(&A.mutex); /* use buffer A */ if(..some_condition..) { use_buffer_b(); } pthread_mutex_unlock(&A.mutex); } /* Thread A */ use_all_buffers(); /* Thread B */ functionB();This application has 2 threads, thread A and thread B. Thread B starts to run first, then thread A starts shortly thereafter. If thread A executes use_all_buffers() and successfully locks A.mutex, it will then block when it tries to lock B.mutex, because thread B has already locked it. While thread B executes functionB and some_condition occurs while thread A is blocked, thread B will now also block trying to acquire A.mutex which is already locked by thread 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 give them up and start again.