IssueDestruction of locked mutex occurs
when a task destroys a mutex after it is locked (and before it is
unlocked). The locking and destruction can happen in the same task
or different tasks.
RiskA mutex is locked to protect shared variables from concurrent
access. If a mutex is destroyed in the locked state, the protection
does not apply.
FixTo fix this defect, destroy the mutex only after you unlock
it. It is a good design practice to:
On the Result Details pane, you see two
events, the locking and destruction of the mutex, and the tasks that
initiated the events. To navigate to the corresponding line in your
source code, click the event.
Example - Locking and Destruction in Different Tasks
#include <pthread.h>
pthread_mutex_t lock1;
pthread_mutex_t lock2;
pthread_mutex_t lock3;
void t0 (void) {
pthread_mutex_lock (&lock1);
pthread_mutex_lock (&lock2);
pthread_mutex_lock (&lock3);
pthread_mutex_unlock (&lock2);
pthread_mutex_unlock (&lock1);
pthread_mutex_unlock (&lock3);
}
void t1 (void) {
pthread_mutex_lock (&lock1);
pthread_mutex_lock (&lock2);
pthread_mutex_destroy (&lock3); //Noncompliant
pthread_mutex_unlock (&lock2);
pthread_mutex_unlock (&lock1);
}
In this example, after task t0
locks the
mutex lock3
, task t1
can destroy
it. The destruction occurs if the following events happen in sequence:
t0
acquires lock3
.
t0
releases lock2
.
t0
releases lock1
.
t1
acquires the lock lock1
released
by t0
.
t1
acquires the lock lock2
released
by t0
.
t1
destroys lock3
.
For simplicity, this example uses a mix of automatic and
manual concurrency detection. The tasks t0
and t1
are manually specified as entry points by using the option Tasks (-entry-points)
.The critical sections are implemented through primitives
pthread_mutex_lock
and pthread_mutex_unlock
that
the software detects automatically. In practice, for entry point specification (thread
creation), you will use primitives such as pthread_create
. The next
example shows how the defect can appear when you use
pthread_create
.
Correction — Place Lock-Unlock Pair Together in Same
Critical Section as DestructionThe locking and destruction of lock3
occurs
inside the critical section imposed by lock1
and lock2
,
but the unlocking occurs outside. One possible correction is to place
the lock-unlock pair in the same critical section as the destruction
of the mutex. Use one of these critical sections:
In this corrected code, the lock-unlock pair and the destruction
is placed in the critical section imposed by lock1
and lock2
.
When t0
acquires lock1
and lock2
, t1
has
to wait for their release before it executes the instruction pthread_mutex_destroy
(&lock3);
. Therefore, t1
cannot destroy
mutex lock3
in the locked state.
#include <pthread.h>
pthread_mutex_t lock1;
pthread_mutex_t lock2;
pthread_mutex_t lock3;
void t0 (void) {
pthread_mutex_lock (&lock1);
pthread_mutex_lock (&lock2);
pthread_mutex_lock (&lock3);
pthread_mutex_unlock (&lock3);
pthread_mutex_unlock (&lock2);
pthread_mutex_unlock (&lock1);
}
void t1 (void) {
pthread_mutex_lock (&lock1);
pthread_mutex_lock (&lock2);
pthread_mutex_destroy (&lock3);
pthread_mutex_unlock (&lock2);
pthread_mutex_unlock (&lock1);
}
Example - Locking and Destruction in Start Routine of Thread#include <pthread.h>
/* Define globally accessible variables and a mutex */
#define NUMTHREADS 4
pthread_t callThd[NUMTHREADS];
pthread_mutex_t lock;
void atomic_operation(void);
void *do_create(void *arg) {
/* Creation thread */
pthread_mutex_init(&lock, NULL);
pthread_exit((void*) 0);
}
void *do_work(void *arg) {
/* Worker thread */
pthread_mutex_lock (&lock);
atomic_operation();
pthread_mutex_unlock (&lock);
pthread_exit((void*) 0);
}
void *do_destroy(void *arg) {
/* Destruction thread */
pthread_mutex_destroy(&lock);//Noncompliant
pthread_exit((void*) 0);
}
int main (int argc, char *argv[]) {
int i;
void *status;
pthread_attr_t attr;
/* Create threads */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
/* Thread that initializes mutex */
pthread_create(&callThd[0], &attr, do_create, NULL);
/* Threads that use mutex for atomic operation*/
for(i=0; i<NUMTHREADS-1; i++) {
pthread_create(&callThd[i], &attr, do_work, (void *)i);
}
/* Thread that destroys mutex */
pthread_create(&callThd[NUMTHREADS -1], &attr, do_destroy, NULL);
pthread_attr_destroy(&attr);
/* Join threads */
for(i=0; i<NUMTHREADS; i++) {
pthread_join(callThd[i], &status);
}
pthread_exit(NULL);
}
In this example, four threads are created. The threads are assigned
different actions.
The first thread callThd[0]
initializes
the mutex lock
.
The second and third threads, callThd[1]
and callThd[2]
,
perform an atomic operation protected by the mutex lock
.
The fourth thread callThd[3]
destroys
the mutex lock
.
The threads can interrupt each other. Therefore, immediately
after the second or third thread locks the mutex, the fourth thread
can destroy it.
Correction — Initialize and Destroy Mutex Outside Start
RoutineOne possible correction is to initialize and destroy the mutex
in the main
function outside the start routine
of the threads. The threads perform only the atomic operation. You
need two fewer threads because the mutex initialization and destruction
threads are not required.
#include <pthread.h>
/* Define globally accessible variables and a mutex */
#define NUMTHREADS 2
pthread_t callThd[NUMTHREADS];
pthread_mutex_t lock;
void atomic_operation(void);
void *do_work(void *arg) {
pthread_mutex_lock (&lock);
atomic_operation();
pthread_mutex_unlock (&lock);
pthread_exit((void*) 0);
}
int main (int argc, char *argv[]) {
int i;
void *status;
pthread_attr_t attr;
/* Create threads */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
/* Initialize mutex */
pthread_mutex_init(&lock, NULL);
for(i=0; i<NUMTHREADS; i++) {
pthread_create(&callThd[i], &attr, do_work, (void *)i);
}
pthread_attr_destroy(&attr);
/* Join threads */
for(i=0; i<NUMTHREADS; i++) {
pthread_join(callThd[i], &status);
}
/* Destroy mutex */
pthread_mutex_destroy(&lock);
pthread_exit(NULL);
}
Correction — Use A Second Mutex To Protect Lock-Unlock
Pair and DestructionAnother possible correction is to use a second mutex and protect
the lock-unlock pair from the destruction. This corrected code uses
the mutex lock2
to achieve this protection. The
second mutex is initialized in the main
function
outside the start routine of the threads.
#include <pthread.h>
/* Define globally accessible variables and a mutex */
#define NUMTHREADS 4
pthread_t callThd[NUMTHREADS];
pthread_mutex_t lock;
pthread_mutex_t lock2;
void atomic_operation(void);
void *do_create(void *arg) {
/* Creation thread */
pthread_mutex_init(&lock, NULL);
pthread_exit((void*) 0);
}
void *do_work(void *arg) {
/* Worker thread */
pthread_mutex_lock (&lock2);
pthread_mutex_lock (&lock);
atomic_operation();
pthread_mutex_unlock (&lock);
pthread_mutex_unlock (&lock2);
pthread_exit((void*) 0);
}
void *do_destroy(void *arg) {
/* Destruction thread */
pthread_mutex_lock (&lock2);
pthread_mutex_destroy(&lock);
pthread_mutex_unlock (&lock2);
pthread_exit((void*) 0);
}
int main (int argc, char *argv[]) {
int i;
void *status;
pthread_attr_t attr;
/* Create threads */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
/* Initialize second mutex */
pthread_mutex_init(&lock2, NULL);
/* Thread that initializes first mutex */
pthread_create(&callThd[0], &attr, do_create, NULL);
/* Threads that use first mutex for atomic operation */
/* The threads use second mutex to protect first from destruction in locked state*/
for(i=0; i<NUMTHREADS-1; i++) {
pthread_create(&callThd[i], &attr, do_work, (void *)i);
}
/* Thread that destroys first mutex */
/* The thread uses the second mutex to prevent destruction of locked mutex */
pthread_create(&callThd[NUMTHREADS -1], &attr, do_destroy, NULL);
pthread_attr_destroy(&attr);
/* Join threads */
for(i=0; i<NUMTHREADS; i++) {
pthread_join(callThd[i], &status);
}
/* Destroy second mutex */
pthread_mutex_destroy(&lock2);
pthread_exit(NULL);
}