pthread_cond_signal () не дает достаточно времени для запуска сигнального потока - PullRequest
0 голосов
/ 10 апреля 2019

Поток, вызывающий pthread_cond_signal, повторно захватывает мьютекс перед тем, как сигнальный поток может быть освобожден.

Приведенный ниже код показывает простой пример рассматриваемой проблемы.Основной поток удерживает блокировку, создает рабочий поток, а затем вводит цикл, который печатает данные по мере их поступления. Сигнализируется на выполнение через условную переменную.

Рабочий поток войдет в цикл, который генерирует данные, захватывает блокировку, записывает данные и затем сигнализирует основному потоку.

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int data = 0;

void *thread(void *arg) {
    int length = *(int *) arg;
    for (int i = 0; i < length; i++) {
        // Do some work
        pthread_mutex_lock(&lock);
        data = i;
        fprintf(stdout, "Writing\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    pthread_exit(0);
}


int main(int argc, const char *argv[]) {
    pthread_t worker;
    int length = 4;

    pthread_mutex_lock(&lock);
    pthread_create(&worker, 0, thread, &length);

    for (int i = 0; i < length; i++) {
        fprintf(stdout, "Waiting\n");
        pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "read data: %d\n", data);
    }
    pthread_mutex_unlock(&lock);

    pthread_join(worker, NULL);
    return 0;
}

Это даст следующий вывод:

Waiting
Writing
Writing
Writing
Writing
read data: 3
Waiting

Ожидаемое поведение: Основной поток содержит мьютекс и освобождает его только после своего ожидания.Затем рабочий поток запишет свои данные и подаст сигнал основному потоку.Основной поток немедленно блокирует мьютекс по сигналу, затем считывает данные и возвращается к ожиданию, освобождая мьютекс.Тем временем рабочий поток выполнит свою работу и будет ждать, пока основной поток снова не начнет записывать свои данные и сигнализировать их.

Вместо этого создается впечатление, что рабочий поток получает мьютекс сразу после сигнала вызова, редкопозволяя основному потоку получить доступ.Если вместо // Do some work я помещу сон в рабочий поток, он выдаст ожидаемый результат.

1 Ответ

2 голосов
/ 10 апреля 2019

Сигнализация условной переменной не дает какого-либо приоритета при блокировке мьютекса потоком, который ожидал эту условную переменную.Все это означает, что по крайней мере один поток, ожидающий переменную условия, начнет пытаться получить мьютекс, чтобы он мог вернуться из pthread_cond_wait().Поток сигнализации продолжит выполнение и может легко заново получить мьютекс, как вы видели.

У вас никогда не должно быть условной переменной без фактического условия для какого-то общего состояния, которого вы ждете -возврат из pthread_cond_wait() не означает, что поток определенно должен продолжаться, это означает, что он должен проверить, является ли условие, которого он ожидал, истинным.Вот почему они называются условными переменными .

. В этом случае состояние, в котором ваш поток записи хочет ждать, равно ", когда основной поток использовал последние записанные мной данные.«.Однако ваш (основной) поток чтения также должен дождаться выполнения условия - "поток записи записал новые данные" .Вы можете выполнить оба эти условия с помощью переменной-флага, которая указывает, что в новую переменную data были записаны некоторые новые неиспользованные данные.Флаг начинается неустановленным, устанавливается записывающим потоком при обновлении data и сбрасывается основным потоком при чтении из data.Поток записи ожидает сброса флага, а поток чтения ожидает установки флага.

При таком расположении вам также не нужно блокировать мьютекс при запуске потока записи.- не имеет значения, в каком порядке запускаются потоки, потому что все в любом случае согласовано.

Обновленный код выглядит следующим образом:

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int data = 0;
int data_available = 0;

void *thread(void *arg)
{
    int length = *(int *) arg;
    for (int i = 0; i < length; i++) {
        // Do some work
        pthread_mutex_lock(&lock);
        fprintf(stdout, "Waiting to write\n");
        while (data_available)
            pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "Writing\n");
        data = i;
        data_available = 1;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    pthread_exit(0);
}


int main(int argc, const char *argv[])
{
    pthread_t worker;
    int length = 4;

    pthread_create(&worker, 0, thread, &length);

    for (int i = 0; i < length; i++) {
        pthread_mutex_lock(&lock);
        fprintf(stdout, "Waiting to read\n");
        while (!data_available)
            pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "read data: %d\n", data);
        data_available = 0;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }

    pthread_join(worker, NULL);
    return 0;
}

Конечно, потоки в конечном итоге работают в режиме ожидания- но по сути у вас есть производитель-потребитель с максимальной длиной очереди 1, так что это ожидаемо.

...