Multiple threads waiting on same condition variable

Используя cnd_signal разбудить один из потоков может привести к неопределенному блокированию

Описание

Этот дефект происходит, когда вы используете cnd_signal семейство функционирует, чтобы разбудить один по крайней мере из двух потоков, которые одновременно ожидают на той же условной переменной. Для потоков с тем же приоритетным уровнем, cnd_signal функции семейства заставляют планировщик потока произвольно просыпаться на потоков, ожидающих на условной переменной, о которой вы предупреждаете с cnd_signal функция семейства.

Средства проверки отмечают cnd_signal вызов функции семейства. См. столбец Event в панели Results Details, чтобы просмотреть потоки, ожидающие на той же условной переменной.

Риск

Поток, который разбужен cnd_signal функция семейства обычно тестирует на предикат условия. В то время как предикат условия является ложным, поток ожидает снова на условной переменной, пока это не разбужено другим потоком, который сигнализирует об условной переменной. Возможно, что программа заканчивается в состоянии, где никакой поток не доступен, чтобы сигнализировать об условной переменной, которая приводит к неопределенному блокированию.

Фиксация

Используйте cnd_broadcast семейство функционирует вместо этого, чтобы разбудить все потоки, ожидающие на условной переменной или использовать различную условную переменную в каждом потоке.

Примеры

развернуть все

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <threads.h>

typedef int thrd_return_t;

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

enum { NTHREADS = 5 };

mtx_t mutex;
cnd_t cond;

thrd_return_t next_step(void* t)
{
    static size_t current_step = 0;
    size_t my_step = *(size_t*)t;

    if (thrd_success != mtx_lock(&mutex)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu has the lock\n", my_step);
    while (current_step != my_step) {
        printf("Thread %zu is sleeping...\n", my_step);
        if (thrd_success !=
            cnd_wait(&cond, &mutex)) {
            /* Handle error */
            fatal_error();
        }
        printf("Thread %zu woke up\n", my_step);
    }
    /* Do processing ... */
    printf("Thread %zu is processing...\n", my_step);
    current_step++;

    /* Signal a waiting task */
    if (thrd_success !=
        cnd_signal(&cond)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu is exiting...\n", my_step);

    if (thrd_success != mtx_unlock(&mutex)) {
        /* Handle error */
        fatal_error();
    }
    return (thrd_return_t)0;
}

int main(void)
{
    thrd_t threads[NTHREADS];
    size_t step[NTHREADS];

    if (thrd_success != mtx_init(&mutex, mtx_plain)) {
        /* Handle error */
        fatal_error();
    }
    if (thrd_success != cnd_init(&cond)) {
        /* Handle error */
        fatal_error();
    }
    /* Create threads */
    for (size_t i = 0; i < NTHREADS; ++i) {
        step[i] = i;
        if (thrd_success != thrd_create(&threads[i],
                                        next_step,
                                        &step[i])) {
            /* Handle error */
            fatal_error();
        }
    }
    /* Wait for all threads to complete */
    for (size_t i = NTHREADS; i != 0; --i) {
        if (thrd_success != thrd_join(threads[i - 1], NULL)) {
            /* Handle error */
            fatal_error();
        }
    }
    (void)mtx_destroy(&mutex);
    (void)cnd_destroy(&cond);
    return 0;
}

В этом примере несколько потоков создаются и присвоили уровень шага. Каждый поток проверяет, совпадает ли его присвоенный уровень шага с текущим уровнем шага (предикат условия). Если предикат является ложным, поток возвращается к ожиданию на условной переменной cond. Использование cnd_signal сигнализировать о cond заставляет планировщик потока произвольно будить один из потоков, ожидающих на cond. Это может привести к неопределенному блокированию, когда предикат условия разбуженного потока является ложным, и никакой другой поток не доступен, чтобы сигнализировать о cond.

Коррекция — использует cnd_broadcast Разбудить Все Потоки

Одна возможная коррекция должна использовать cnd_broadcast вместо этого сигнализировать о cond. Функциональный cnd_signal будит весь поток, которые ожидают на cond.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <threads.h>

typedef int thrd_return_t;

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

enum { NTHREADS = 5 };

mtx_t mutex;
cnd_t cond;

thrd_return_t next_step(void* t)
{
    static size_t current_step = 0;
    size_t my_step = *(size_t*)t;

    if (thrd_success != mtx_lock(&mutex)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu has the lock\n", my_step);
    while (current_step != my_step) {
        printf("Thread %zu is sleeping...\n", my_step);
        if (thrd_success !=
            cnd_wait(&cond, &mutex)) {
            /* Handle error */
            fatal_error();
        }
        printf("Thread %zu woke up\n", my_step);
    }
    /* Do processing ... */
    printf("Thread %zu is processing...\n", my_step);
    current_step++;

    /* Signal a waiting task */
    if (thrd_success !=
        cnd_broadcast(&cond)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu is exiting...\n", my_step);

    if (thrd_success != mtx_unlock(&mutex)) {
        /* Handle error */
        fatal_error();
    }
    return (thrd_return_t)0;
}

int main_test_next_step(void)
{
    thrd_t threads[NTHREADS];
    size_t step[NTHREADS];

    if (thrd_success != mtx_init(&mutex, mtx_plain)) {
        /* Handle error */
        fatal_error();
    }
    if (thrd_success != cnd_init(&cond)) {
        /* Handle error */
        fatal_error();
    }
    /* Create threads */
    for (size_t i = 0; i < NTHREADS; ++i) {
        step[i] = i;
        if (thrd_success != thrd_create(&threads[i],
                                        next_step,
                                        &step[i])) {
            /* Handle error */
            fatal_error();
        }
    }
    /* Wait for all threads to complete */
    for (size_t i = NTHREADS; i != 0; --i) {
        if (thrd_success != thrd_join(threads[i - 1], NULL)) {
            /* Handle error */
            fatal_error();
        }
    }
    (void)mtx_destroy(&mutex);
    (void)cnd_destroy(&cond);
    return 0;
}

Информация о результате

Группа: параллелизм
Язык: C | C++
Значение по умолчанию: 'off'
Синтаксис командной строки: SIGNALED_COND_VAR_NOT_UNIQUE
Удар: низко

Введенный в R2020a