Producer-Consumer w / pthreads + семафоры в C (одновременный доступ к слотам буфера) - PullRequest
1 голос
/ 04 апреля 2019

Я написал многопоточную программу «производитель-потребитель» на C. Моя первоначальная программа не была Minimal / Complete / Verifiable, поэтому я переписал ее с нуля, и ошибка все еще происходит.Я обнаружил, что несколько потоков производителей пытаются создать одни и те же данные (из последовательности данных), и я не уверен, как это остановить.

Некоторые вещи, которые я пробовал: - Я создалмассив bool того же размера, что и буфер, чтобы убедиться, что потоки только помещают данные в пустой слот, и берут из одного с данными в нем, это решило одну проблему - я попытался переместить приращение слота ввода / вывода в началоцикл «производитель / потребитель» выполняет циклы (соответственно), но это вызывает тупик

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

ОБРАТИТЕ ВНИМАНИЕ: я изучаю C в течение одного месяца, и это также мой первый пост здесь.Я многому научился на этом сайте за последние несколько лет, и я знаю, что иногда ответы здесь могут быть довольно резкими, поэтому, пожалуйста, будьте добры к тому, кто искренне хочет учиться и совершенствоваться.Спасибо!:)

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

char * Buffer;
int nBufferSlots = 10;
int nThreads = 2;
pthread_t Producers, Consumers;
sem_t Empty, Full, Mutex;
int inputSlot;
int outputSlot;
char value = 0;
char SlotOccupationBoolArray[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

void * producer(void *args)
{
    while(1)
    {
        sleep( rand() % 3);

        value++;

        sem_wait(&Empty);
        sem_wait(&Mutex);

        if (SlotOccupationBoolArray[inputSlot] == 0)
        {
            Buffer[inputSlot] = value;
            SlotOccupationBoolArray[inputSlot] = 1;
        }
        else
        {
            value--;
        }

        inputSlot = (inputSlot + 1) % nBufferSlots;

        sem_post(&Mutex);
        sem_post(&Full);

        printf("\n[ ");
        for (int i=0;i<10;i++)
        {
            printf("%d ", Buffer[i]);
        }
        printf("]\n");
    }
}
void * consumer(void *args)
{
    while(1)
    {
        sleep( rand() % 5);

        sem_wait(&Full);
        sem_wait(&Mutex);

        if (SlotOccupationBoolArray[outputSlot] == 1)
        {
            char ValueConsumed = Buffer[outputSlot];
            printf("\nConsumed value: %d\n", ValueConsumed);
            Buffer[outputSlot] = 0;
            SlotOccupationBoolArray[outputSlot] = 0;
        }

        outputSlot = (outputSlot + 1) % nBufferSlots;

        sem_post(&Mutex);
        sem_post(&Empty);
    }
}

int main(void)
{
    sem_open("/Mutex", O_CREAT, S_IRUSR | S_IWUSR, 1);
    sem_open("/Full", O_CREAT, S_IRUSR | S_IWUSR, 0);

    Buffer = (char*) malloc(nBufferSlots*sizeof(char));

    sem_open("/Empty", O_CREAT, S_IRUSR | S_IWUSR, nBufferSlots);

    Producers = (pthread_t) malloc(nThreads*sizeof(pthread_t));
    Consumers = (pthread_t) malloc(nThreads*sizeof(pthread_t));

    // set charBuffer elements to 0
    for (char i=0;i<10;i++)
    {
        Buffer[i] = 0;
    }

    for (int i = 0; i < nThreads; i++)
    {
        pthread_create(&Producers, NULL, producer, NULL);
        pthread_create(&Consumers, NULL, consumer, NULL);
    }

    for(int i=0;i<nThreads;i++){
        int err = pthread_create(&Producers,NULL,producer,NULL);
        if(err != 0){
            printf("Error creating producer %d\n",i+1);
        }else{
            printf("Successfully created producer %d\n",i+1);
        }
    }

    for(int i=0;i<nThreads;i++){
        int err = pthread_create(&Consumers,NULL,consumer,NULL);
        if(err != 0){
            printf("Error creating consumer %d\n",i+1);
        }else{
            printf("Successfully created consumer %d\n",i+1);
        }
    }

    pthread_exit(NULL);
}

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

Что бы я хотелхотелось бы видеть в выводе что-то вроде этого:

  • [1 0 0 0 0 0 0 0 0 0]
  • [1 2 0 0 0 0 0 0 0 0]
  • [1 2 3 0 0 0 0 0 0 0]
  • Потребляемая стоимость: 1
  • [0 2 3 4 0 0 0 0 0 0]
  • [0 2 3 4 5 0 0 0 0 0]
  • Потребляемая стоимость: 3
  • [0 2 0 4 5 0 0 0 0 0]
  • Потребляемая стоимость: 4
  • [0 2 0 0 5 6 0 0 0 0]
  • [0 2 0 0 5 6 7 0 0 0]

Но вместо этогоЯ вижу что-то вроде этого (дубликат 2/4 - это проблема):

  • [2 2 4 4 5 6 0 0 0 0]
  • [2 2 4 4 56 7 0 0 0]
  • [2 2 4 4 5 6 7 8 0 0]
  • Потребляемая стоимость: 2
  • Потребляемая стоимость: 2
  • [0 0 4 4 5 6 7 8 9 0]
  • Потребляемая стоимость: 4

(Извинения заформатирование)

1 Ответ

0 голосов
/ 07 апреля 2019

Значения пропускаются и / или повторяются, потому что в функции producer() value++ выполняется вне мьютекса / семафоров, а чтение значения, а также value-- выполняется внутри.

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

В вашей программе возможно выполнение следующей последовательности:

  1. Начальное значение =0.Нить продюсер-1 приходит и делает value++.Теперь значение = 1.Он получает и Empty, и Mutex.
  2. До того, как Producer-1 записывает значение в буферный слот, Producer-2 получает расписание и выполняет value++.Теперь значение = 2.Producer-2 получит Empty, но он должен ждать Mutex, поскольку он заблокирован Producer-1.
  3. Теперь Producer-1 снова запланирован.Поскольку value=2, в этот момент он записывает то же самое в буферный слот.После записи он разблокирует Empty и Mutex.
  4. . После этого Producer-2 получит расписание.Это получает Mutex, поскольку Производитель-1 разблокировал это.Тем не менее value=2, поэтому он будет записывать то же самое в буферный слот.
  5. Обратите внимание: если другой поток производителя запланирован до записи Producer-2, value все еще может измениться.Поскольку значение обновляется за пределами Mutex, нет ограничений на то, сколько раз оно может быть изменено / число пропущено.

Итак, каково решение этой проблемы?

Переместите значение обновления кода (или любой общий ресурс, чтобы быть универсальным) в мьютекс в producer().В вашем коде вам не нужно сначала делать value++, а если слот недоступен, делать value--.Лучшее решение - обновить значение при записи в слот буфера, как показано ниже.

    //value++; //Don't update it here. Move it to critical section of the code, inside mutex.

    sem_wait(&Empty);
    sem_wait(&Mutex);

    if (SlotOccupationBoolArray[inputSlot] == 0)
    {
        Buffer[inputSlot] = ++value; //Update and assign value, Read about pre-increment
        SlotOccupationBoolArray[inputSlot] = 1;
    }
    //else //This section is not required.
    //{
    //    value--;
    //}

Помимо этого, есть еще одна проблема.sem_post для Full должно происходить, когда источник записывает данные в слот в буфере.Точно так же sem_post для Empty должно произойти, когда потребитель читает из слота в буфере.

Но вы делаете sem_post в каждом цикле.Это вызовет некоторые проблемы.Так что позаботься об этом тоже.Делайте sem_post для Full / Empty только тогда, когда происходит чтение / запись.

PS: мьютекс и семафоры - разные понятия.Использование их надлежащим образом в зависимости от варианта использования важно для хорошего, эффективного и более удобочитаемого программирования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...