ПроблемаЭта проблема возникает при использовании cnd_signal
функции семейства, чтобы пробудить один из по крайней мере двух потоков, которые одновременно ожидают одной и той же переменной условия. Для потоков с таким же уровнем приоритета, cnd_signal
функции семейства заставляют планировщик потоков произвольно просыпаться в потоках, ожидающих от переменной условия, которую вы сигнализируете со cnd_signal
функции семейства.
Шашки помечают cnd_signal
вызов функции семейства. Смотрите столбец Event на панели Results Details, чтобы просмотреть потоки, ожидающие той же переменной условия.
РискНить, которая просыпается с cnd_signal
функция семейства обычно проверяет на предикат условия. В то время как предикат условия ложен, поток снова ждет от переменной условия, пока он не пробуждается другим потоком, который сигнализирует о переменной условия. Возможно, что программа оказывается в состоянии, где нет потока, доступного для сигнала переменной условия, что приводит к неопределенной блокировке.
ЗафиксироватьИспользование cnd_broadcast
функции семейства вместо этого, чтобы пробудить все потоки, ожидающие переменной условия, или использовать другую переменную условия для каждого потока.
Пример - Использование cnd_signal
Чтобы пробудить один из многих потоков, ожидающих переменной условия#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;
}