Contenuto principale

Multiple mutexes used with same condition variable

Threads using different mutexes when concurrently waiting on the same condition variable is undefined behavior

Description

This checker is deactivated in a default Polyspace® as You Code™ analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace as You Code).

This defect occurs when multiple threads use more than one mutex to concurrently wait on the same condition variable. A thread waits on a condition variable by calling these functions:

  • pthread_cond_timedwait or pthread_cond_wait (POSIX)

  • cnd_wait or cnd_timedwait (C11)

  • _Cnd_wait or _Cnd_timedwait (Visual C11)

  • SleepConditionVariableCS (WinAPI)

  • std::condition_variable::wait (C++)

These functions take a condition variable and a locked mutex as arguments, and the condition variable is bound to that mutex when the thread waits on the condition variable.

In the Results Details pane, see the Event column to view the threads waiting on the same condition variable and using a different mutex.

Risk

When a thread waits on a condition variable using a mutex, the condition variable is bound to that mutex. Any other thread using a different mutex to wait on the same condition variable is undefined behavior according to the POSIX® standard.

Fix

Associate unique condition variable to each mutex.

Examples

expand all

In this example, a different mutex is used to protect each count variable. Since all three waiter functions wait on the same condition variable cv with different mutexes, the call to pthread_cond_wait will succeed for one of the threads and the call will be undefined for the other two.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define Thrd_return_t                    void *
#define __USE_XOPEN2K8



#define COUNT_LIMIT 5

static void fatal_error(void)
{
    exit(1);
}


pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
pthread_mutex_t mutex3;
pthread_cond_t cv;

int count1 = 0, count2 = 0, count3 = 0;
#define DELAY 8

Thrd_return_t waiter1(void* arg)
{
    int ret;
    while (count1 < COUNT_LIMIT) {
        if ((ret = pthread_mutex_lock(&mutex1)) != 0) {
            /* Handle error */
            fatal_error();
        }
        if ((ret =
                 pthread_cond_wait(&cv, &mutex1)) != 0) {
            /* Handle error */
            fatal_error();
        }
        sleep(random() % DELAY);
        printf("count1 = %d\n", ++count1);
        if ((ret = pthread_mutex_unlock(&mutex1)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}

Thrd_return_t waiter2(void* arg)
{
    int ret;
    while (count2 < COUNT_LIMIT) {
        if ((ret = pthread_mutex_lock(&mutex2)) != 0) {
            /* Handle error */
            fatal_error();
        }
        if ((ret =
                 pthread_cond_wait(&cv, &mutex2)) != 0) {
            /* Handle error */
            fatal_error();
        }
        sleep(random() % DELAY);
        printf("count2 = %d\n", ++count2);
        if ((ret = pthread_mutex_unlock(&mutex2)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}

Thrd_return_t signaler(void* arg)
{
    int ret;
    while ((count1 < COUNT_LIMIT) || (count2 < COUNT_LIMIT)) {
        sleep(1);
        printf("signaling\n");
        if ((ret = pthread_cond_broadcast(&cv)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}

Thrd_return_t waiter3(void* arg)
{
    int ret;
    while (count3 % COUNT_LIMIT != 0) {
        if ((ret = pthread_mutex_lock(&mutex3)) != 0) {
            /* Handle error */
            fatal_error();
        }
        if ((ret =
                 pthread_cond_wait(&cv, &mutex3)) != 0) {
            /* Handle error */
            fatal_error();
        }
        sleep(random() % DELAY);
        printf("count3 = %d\n", ++count3);
        if ((ret = pthread_mutex_unlock(&mutex3)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}

int main(void)
{
    int ret;
    pthread_t thread1, thread2, thread3;

    pthread_mutexattr_t attr;

    if ((ret = pthread_mutexattr_init(&attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
        /* Handle error */
        fatal_error();
    }

    if ((ret = pthread_mutex_init(&mutex1, &attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_mutex_init(&mutex2, &attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_mutex_init(&mutex3, &attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_cond_init(&cv, NULL)) != 0) {
        /* handle error */
        fatal_error();
    }
    if ((ret = pthread_create(&thread1, NULL, &waiter1, NULL))) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_create(&thread2, NULL, &waiter2, NULL))) {
        /* handle error */
        fatal_error();
    }
    if ((ret = pthread_create(&thread3, NULL, &signaler, NULL))) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_join(thread1, NULL)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_join(thread2, NULL)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_join(thread3, NULL)) != 0) {
        /* Handle error */
        fatal_error();
    }

    while (1) { ; }

    return 0;
}

The checker raises a defect for function waiter3 even though the function is not invoked directly or indirectly by a thread, entry-point, or interrupt. The analysis considers function waiter3 called by the main program through its function address or an unidentified thread whose creation is the missing source code.

Correction — Use the Same Mutex for All Threads Waiting on Same Condition Variable

One possible correction is to pass the same mutex argument to all the call to pthread_cond_wait that are used to wait on the same condition variable.

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define Thrd_return_t                    void *
#define __USE_XOPEN2K8



#define COUNT_LIMIT 5

static void fatal_error(void)
{
    exit(1);
}


pthread_mutex_t mutex;

pthread_cond_t cv;

int count1 = 0, count2 = 0, count3 = 0;
#define DELAY 8

Thrd_return_t waiter1(void* arg)
{
    int ret;
    while (count1 < COUNT_LIMIT) {
        if ((ret = pthread_mutex_lock(&mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
        if ((ret =
                 pthread_cond_wait(&cv, &mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
        sleep(random() % DELAY);
        printf("count1 = %d\n", ++count1);
        if ((ret = pthread_mutex_unlock(&mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}

Thrd_return_t waiter2(void* arg)
{
    int ret;
    while (count2 < COUNT_LIMIT) {
        if ((ret = pthread_mutex_lock(&mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
        if ((ret =
                 pthread_cond_wait(&cv, &mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
        sleep(random() % DELAY);
        printf("count2 = %d\n", ++count2);
        if ((ret = pthread_mutex_unlock(&mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}

Thrd_return_t signaler(void* arg)
{
    int ret;
    while ((count1 < COUNT_LIMIT) || (count2 < COUNT_LIMIT)) {
        sleep(1);
        printf("signaling\n");
        if ((ret = pthread_cond_broadcast(&cv)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}

Thrd_return_t waiter3(void* arg)
{
    int ret;
    while (count3 % COUNT_LIMIT != 0) {
        if ((ret = pthread_mutex_lock(&mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
        if ((ret =
                 pthread_cond_wait(&cv, &mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
        sleep(random() % DELAY);
        printf("count3 = %d\n", ++count3);
        if ((ret = pthread_mutex_unlock(&mutex)) != 0) {
            /* Handle error */
            fatal_error();
        }
    }
    return (Thrd_return_t)0;
}
/* 
void user_task(void)
{
    (void)waiter3(NULL);
} */

int main(void)
{
    int ret;
    pthread_t thread1, thread2, thread3;

    pthread_mutexattr_t attr;

    if ((ret = pthread_mutexattr_init(&attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
        /* Handle error */
        fatal_error();
    }

    if ((ret = pthread_mutex_init(&mutex, &attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_mutex_init(&mutex, &attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_mutex_init(&mutex, &attr)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_cond_init(&cv, NULL)) != 0) {
        /* handle error */
        fatal_error();
    }
    if ((ret = pthread_create(&thread1, NULL, &waiter1, NULL))) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_create(&thread2, NULL, &waiter2, NULL))) {
        /* handle error */
        fatal_error();
    }
    if ((ret = pthread_create(&thread3, NULL, &signaler, NULL))) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_join(thread1, NULL)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_join(thread2, NULL)) != 0) {
        /* Handle error */
        fatal_error();
    }
    if ((ret = pthread_join(thread3, NULL)) != 0) {
        /* Handle error */
        fatal_error();
    }

    while (1) { ; }

    return 0;
}

In this example, mutexes myMutex1 and myMutex2 both concurrently wait on the condition variable myCond. When this variables is signaled in sync(), it is undefined which mutex is unlocked. Polyspace reports this defect.

#include <stdint.h>
#include <threads.h>
#include <stdio.h>
#include <time.h>

mtx_t myMutex1;
mtx_t myMutex2;
cnd_t myCond;
int32_t foo(void) {
	mtx_lock(&myMutex1);
	cnd_wait(&myCond, &myMutex1); //Noncompliant
	mtx_unlock(&myMutex1);
	return 0;
}

int32_t bar(void) {
	mtx_lock(&myMutex2);
	cnd_wait(&myCond, &myMutex2); //Noncompliant
	mtx_unlock(&myMutex2);
	return 0;
}

int32_t sync(void){
    //...
    cnd_signal(&myCond);
    //..
}
int main(void) {
    // Initialize mutexes and condition variable
    mtx_init(&myMutex1, mtx_plain);
    mtx_init(&myMutex2, mtx_plain);
    cnd_init(&myCond);
    thrd_t thread1, thread2, thread3;
    
    // Create threads
    thrd_create(&thread1, foo, NULL);
    thrd_create(&thread2, bar, NULL);
    
    // Give time for threads foo and bar to wait
    thrd_sleep(&(struct timespec){.tv_sec = 1, .tv_nsec = 0}, NULL);
    
    // Create thread sync to signal the condition variable
    thrd_create(&thread3, sync, NULL);
    
    // Wait for threads to finish
    thrd_join(thread1, NULL);
    thrd_join(thread2, NULL);
    thrd_join(thread3, NULL);

    // Destroy mutexes and condition variable
    mtx_destroy(&myMutex1);
    mtx_destroy(&myMutex2);
    cnd_destroy(&myCond);
    return 0;

}
Correction

Use unique condition variable for each mutex.

#include <stdint.h>
#include <threads.h>
#include <stdio.h>
#include <time.h>

mtx_t myMutex1;
mtx_t myMutex2;
cnd_t myCond1;
cnd_t myCond2;
int32_t foo(void) {
	mtx_lock(&myMutex1);
	cnd_wait(&myCond1, &myMutex1); //Compliant
	mtx_unlock(&myMutex1);
	return 0;
}

int32_t bar(void) {
	mtx_lock(&myMutex2);
	cnd_wait(&myCond2, &myMutex2); //Compliant
	mtx_unlock(&myMutex2);
	return 0;
}

int32_t sync(void){
    //...
    cnd_signal(&myCond1);
    cnd_signal(&myCond2);
    //..
}
int main(void) {
    // Initialize mutexes and condition variable
    mtx_init(&myMutex1, mtx_plain);
    mtx_init(&myMutex2, mtx_plain);
    cnd_init(&myCond1);
    cnd_init(&myCond2);
    thrd_t thread1, thread2, thread3;
    
    // Create threads
    thrd_create(&thread1, foo, NULL);
    thrd_create(&thread2, bar, NULL);
    
    // Give time for threads foo and bar to wait
    thrd_sleep(&(struct timespec){.tv_sec = 1, .tv_nsec = 0}, NULL);
    
    // Create thread sync to signal the condition variable
    thrd_create(&thread3, sync, NULL);
    
    // Wait for threads to finish
    thrd_join(thread1, NULL);
    thrd_join(thread2, NULL);
    thrd_join(thread3, NULL);

    // Destroy mutexes and condition variable
    mtx_destroy(&myMutex1);
    mtx_destroy(&myMutex2);
    cnd_destroy(&myCond1);
    cnd_destroy(&myCond2);
    return 0;

}

Result Information

Group: Concurrency
Language: C | C++
Default: Off
Command-Line Syntax: MULTI_MUTEX_WITH_ONE_COND_VAR
Impact: Medium
PQL Name: std.defects.MULTI_MUTEX_WITH_ONE_COND_VAR

Version History

Introduced in R2020a