Реализация производителей / потребителей, использующих мьютекс - PullRequest
1 голос
/ 09 июля 2010
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#define WORK_SIZE 1024
pthread_mutex_t work_mutex;
char work_area[WORK_SIZE];
void *thread_start(void *);
int main() {
pthread_t a_thread;
pthread_mutex_init(&work_mutex,NULL);
pthread_create(&a_thread,NULL,thread_start,NULL);
while(1)
{
pthread_mutex_lock(&work_mutex);
printf("Enter some text\n");
fgets(work_area, WORK_SIZE, stdin);
pthread_mutex_unlock(&work_mutex);
}
return 0;
}

void *thread_start(void *arg)
{
sleep(1);
while(1)
{
pthread_mutex_lock(&work_mutex);
printf("You enetered %d char",strlen(work_area));
pthread_mutex_unlock(&work_mutex);
}
}

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

Чтобы быть более понятным, я получаю такой тип вывода: -

Enter some text
qwerty
Enter some text
asdaf
Enter some text
jkdf 
Enter some text

Ответы [ 6 ]

2 голосов
/ 09 июля 2010

Вам так кажется.Вы блокируете перед вводом данных в main, который будет на несколько порядков больше, чем требуется для вывода строки.За все это время другой поток не будет делать ничего, кроме блокировки.Ваша главная задача освободит блокировку, а микросекунды снова приобретут ее.

Если вы сделаете это достаточно долго - возможно, тысячи раз - вы увидите, как это работает.Но было бы лучше просто скопировать строку ввода в main в очередь или какой-либо другой фрагмент памяти, защищенный блокировкой.Тогда у другого потока будет шанс добраться до него.

РЕДАКТИРОВАТЬ:

Общая идея заключается в следующем.Мои добавления кода ужасны, но для иллюстрации должны работать достаточно хорошо.

int main()
{
    pthread_t a_thread;
    pthread_mutex_init(&work_mutex, NULL);
    pthread_create(&a_thread, NULL, thread_start, NULL);

    memset(work_area, '\0', sizeof(work_area));


    char input[WORK_SIZE - 1];

    while (1)
    {
        printf("Enter some text\n");
        fgets(input, WORK_SIZE, stdin);

        pthread_mutex_lock(&work_mutex);
        strcpy(work_area, input);
        pthread_mutex_unlock(&work_mutex);
    }

    return 0;
}

void *thread_start(void *arg)
{
    sleep(1);

    while (1)
    {
        pthread_mutex_lock(&work_mutex);

        if (work_area[0] != '\0')
        {
            printf("You enetered %d char\n", strlen(work_area));
            work_area[0] = '\0';
        }

        pthread_mutex_unlock(&work_mutex);
    }
}
1 голос
/ 09 июля 2010

С учетом предложения использовать семафор, я думаю, что причина поведения, которое вы наблюдаете, заключается в следующем.

  1. Пока не вызывается pthread_mutex_lock в thread_start, цикл вГлавное не будет заблокировано.
  2. Следовательно, единственный раз, когда цикл thread_start получит возможность вызвать pthread_mutex_lock, это когда временной интервал потока, выполняющего main, истекает
  3. Шансы истечения этого временного интервала происходят, покаблокировка разблокирована.Это связано с тем, что основной поток, вероятно, будет иметь новый временной интервал, когда он выйдет из заблокированного состояния, пока он ожидает клавишу ВВОД.

Обратите внимание, что это объяснение предполагает одно ядро.Но даже в многоядерной системе поток thread_start будет планироваться только тогда, когда закончится временной интервал другого потока.Вероятность того, что это произойдет, пока поток main не удерживает блокировку, мала.

Один из возможных тестов для моей вышеупомянутой гипотезы - вызвать pthread_yield после снятия блокировки.Возможно, вы захотите сделать это в обоих потоках.Даже тогда я не думаю, что это ГАРАНТИРУЕТ переключение потоков каждый раз.

1 голос
/ 09 июля 2010

Возможно, вы захотите создать и инициализировать семафор, а затем подождать во 2-м потоке, пока основная функция не сообщит о событии, когда на него подается вход.

Проверка условного ожидания и семафоров.

Второй поток не знает, какое событие генерируется в основном потоке. Для справки .

0 голосов
/ 09 ноября 2010

@ torax * «Обратите внимание, это объяснение предполагает одно ядро. Но даже в многоядерной системе поток thread_start будет планироваться только тогда, когда закончится временной интервал другого потока» *

Я думаю, что это не так, основной поток и thread_start могут быть запланированы параллельно на обоих ядрах

0 голосов
/ 09 июля 2010

Вот немного более неловкое решение, которое гарантированно сработает: #include

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

#define WORK_SIZE 1024
pthread_mutex_t work_mutex;
char input_area[WORK_SIZE];
char work_area[WORK_SIZE];

void *thread_start(void *);

int main()
{
    pthread_t a_thread;
    pthread_mutex_init(&work_mutex,NULL);
    work_area[0] = 0;

    pthread_create(&a_thread,NULL,thread_start,NULL);
    while(1) {
        printf("Enter some text\n");
        fgets(input_area, WORK_SIZE, stdin);
        pthread_mutex_lock(&work_mutex);
        while (work_area[0] != 0) {
            pthread_mutex_unlock(&work_mutex);
            sleep(1);
            pthread_mutex_lock(&work_mutex);
        }
        memcpy(work_area, input_area, WORK_SIZE);
        pthread_mutex_unlock(&work_mutex);
    }
    return 0;
}

void *thread_start(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&work_mutex);
        if (work_area[0] > 0) {
            printf("you enetered %d char\n",strlen(work_area));
            work_area[0] = 0;
            pthread_mutex_unlock(&work_mutex);
        } else {
            pthread_mutex_unlock(&work_mutex);
            sleep(1);
        }
    }
}

Обратите внимание, что, поскольку мьютексные блокировки POSIX зависят от потока, потребительский поток не может ждать сигнала от потока производителя, поэтому здесь он просто просыпается каждую секунду, чтобы проверить наличие новых данных. Точно так же, если производителю необходимо поместить что-то в очередь, а рабочая область заполнена, он ждет в течение секунд, пока потребитель не приступит к опустошению буфера. Вы можете избавиться от некоторых задержек, используя pthread_yield вместо sleep, но тогда ваши потоки будут «заняты-ждут», потребляя много ресурсов ЦП, чтобы снова и снова проверять их состояние на соответствие

Обратите внимание, что если вы хотите, чтобы он печатал строку для 0-символьных записей, вы можете добавить отдельное значение bool, указывающее, есть ли новые данные в очереди.

0 голосов
/ 09 июля 2010

Прочитав комментарии, данные тораком, я изменил код. Теперь он работает нормально, как и ожидалось.

[root@localhost threads]# diff -Nurp mutex.c mutex_modified.c 
--- mutex.c 2010-07-09 19:50:51.000000000 +0530
+++ mutex_modified.c    2010-07-09 19:50:35.000000000 +0530
@@ -18,6 +18,7 @@ pthread_mutex_lock(&work_mutex);
 printf("Enter some text\n");
 fgets(work_area, WORK_SIZE, stdin);
 pthread_mutex_unlock(&work_mutex);
+sleep(1);
 }
 return 0;
 }
@@ -30,5 +31,6 @@ while(1)
 pthread_mutex_lock(&work_mutex);
 printf("You enetered %d char",strlen(work_area));
 pthread_mutex_unlock(&work_mutex);
+sleep(1);
 }
 }

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

...