C - потребитель/производитель оказывается в тупике, когда производитель превышает размер буфера

У меня есть задание работать над проблемой производителя и потребителя, используя поток и семафор. Задача состоит в том, чтобы позволить пользователю определить # производителя, # потребителя и размер буфера. Программа всегда блокируется, если производитель достигает размера буфера. Но в требовании говорится, что если производитель достигает размера буфера, поток потребителя должен запускаться и брать данные из буфера. Я понятия не имею, как решить эту проблему, и мой учитель отказывается помочь. Я совсем новичок в языке C, пожалуйста, дайте мне какое-нибудь предложение. Большое Вам спасибо

Моя программа может работать, когда Producer = Consumer или Producer ‹ Consumer, за исключением Producer > Buffer Size, кажется, что это тупиковая ситуация, и я думаю, что понимаю причину, но я не знаю, как исправить код, чтобы позволить потоку Consumer работать сначала, чем вернуться к потоку Producer.

Вот текущий результат, когда производитель = 3, потребитель = 1 и размер буфера = 2.

./Task2 3 1 2
Producer 0 has started
Producer 0:Put item 0.
Producer 1 has started
Producer 1:Put item 1.
Producer 2 has started

Требование говорит, что результат должен выглядеть так

Started
Producer 0 has started
Producer 0: Put item 0.
Producer 1 has started
Producer 1: Put item 1.
Producer 2 has started
Consumer 0 has started
Consumer 0: Taked item 0.
Producer 2: Put item 2.
Terminated!

Вот мой исходный код, я отбросил код проверки ошибок ввода

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

pthread_t *pid, *cid;

void *producer(void *param); 
void *consumer(void *param); 
void init();
int Remove();

struct prot_buffer{
int Producer;
int Consumer;
int *buffer;        
int buffersize;
int front;      
int rear;           
int item;           
sem_t mutex;        
sem_t slots;
sem_t items;
}b;


main(int argc, char *argv[]){
int c1; 

b.Producer = atoi(argv[1]);
b.Consumer = atoi(argv[2]);
b.buffersize = atoi(argv[3]);

init(); 

pid = (pthread_t *)malloc(b.Producer *sizeof(pthread_t));
cid = (pthread_t *)malloc(b.Consumer *sizeof(pthread_t));

for (c1=0; c1< b.Producer; c1++){
    printf("Producer %d has started\n", c1);
    pthread_create(&(pid[c1]),NULL, producer, NULL);
    pthread_join(pid[c1], NULL);
    printf("Producer %d:Create item %d.\n", c1,c1);
}


/* Create the consumer threads */
for (c1=0; c1<b.Consumer; c1++){
    printf("Consumer %d has started\n", c1);
    pthread_create(&(cid[c1]),NULL, consumer, NULL);
        if (b.front==b.rear){
        printf("Terminated!\n");
        exit(0);
        }  
    pthread_join(cid[c1], NULL);
    printf("Consumer %d:Taked item %d.\n", c1, c1);
}  

free(b.buffer);
free(pid);
free(cid);

sem_destroy(&b.items);
sem_destroy(&b.slots);
sem_destroy(&b.mutex);

printf("Threads terminated!\n");
exit(0);
}

void *producer(void *param){
sem_wait(&b.slots);                             sem_wait(&b.mutex);

if(b.rear<=b.buffersize){
    b.buffer[b.rear] = b.item; 
    b.rear++;
    sem_post(&b.mutex);                             sem_post(&b.items);                     
}else{
sem_post(&b.mutex);                             sem_post(&b.items);                             }
}

void *consumer(void *param){
Remove();
}

void init(){
b.buffer = (int *) malloc(b.buffersize *sizeof(int));
b.buffersize = b.buffersize;
b.front = b.rear =0;
sem_init(&b.items, 0, 0);
sem_init(&b.slots,0,b.buffersize);
sem_init(&b.mutex, 0, 1);
}

int Remove(){
sem_wait(&b.items);                     
sem_wait(&b.mutex);                     

b.item = b.buffer[b.front];             
b.front++;

sem_post(&b.mutex);                     
sem_post(&b.slots);                     
return b.item;
}

Мой новый код

main(int argc, char *argv[]){
...

pthread_create(&pid,NULL, producer, NULL);
pthread_create(&cid,NULL, consumer, NULL);
....
}

void *producer(void *param){
int c2;

for (c2=0; c2 < b.Producer; c2++) {
printf("Producer %d has started\n", c2);
b.item = c2;

sem_wait(&b.slots);
sem_wait(&b.mutex); 

b.buffer[b.rear] = b.item;
b.rear = (b.rear+1)%b.buffersize;
printf("Producer %d:Put item %d.\n", c2,c2);

sem_post(&b.mutex);
sem_post(&b.items);

}
return NULL;
}

void *consumer(void *param){
int c2;

for (c2=0; c2 < b.Consumer; c2++) {
printf("Consumer %d has started\n", c2,c2);
b.item = c2;

sem_wait(&b.items);
sem_wait(&b.mutex); 

b.buffer[b.front] = b.item;
b.front = (b.front+1)%b.buffersize;

printf("Consumer %d:take item %d.\n", c2, c2);

sem_post(&b.mutex);
sem_post(&b.slots);

}
return NULL;
}

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

Результат программы теперь правильный, спасибо за помощь. В этом случае я использую b.item как переменную для отображения элемента, оставшегося внутри буфера, но это неправильно. Использование других переменных, таких как передняя или задняя, ​​также не работает.

Результат программы-

Производитель=2, Потребитель=2, Буфер=2

./F 2 2 2
started
Producer 0 has started
Producer 0:Put item 0.
Producer 1 has started
Producer 1:Put item 1.
Consumer 0 has started
Consumer 0:Take item 0.
Consumer 1 has started
Consumer 1:Take item 1.
1 item(s) left in the buffer!  //This is wrong!
Terminated!

Производитель=3, Потребитель=1, Буфер=2

./F 3 1 2
started
Producer 0 has started
Producer 0:Deposited item 0.
Producer 1 has started
Producer 1:Deposited item 1.
Producer 2 has started
Consumer 0 has started
Consumer 0:Removed item 0.
Producer 2:Deposited item 2.
0 item(s) left in the buffer!  //Still wrong!
Terminated!

Производитель = 2, Потребитель = 5, Буфер = 3

./F 2 5 3
started
Producer 0 has started
Producer 0:Put item 0.
Producer 1 has started
Producer 1:Put item 1.
Consumer 0 has started
Consumer 0:Take item 0.
Consumer 1 has started
Consumer 1:Take item 1.
Consumer 2 has started
2 item(s) left in the buffer!  //Wrong again!
Terminated!

person Shawn Lien    schedule 27.03.2012    source источник
comment
Приведение возвращаемого значения malloc не требуется.   -  person Daniel Kamil Kozar    schedule 27.03.2012
comment
Вы ограничены в использовании семафора? Вы не можете использовать условные переменные? Они кажутся гораздо более естественным выбором для этой ситуации.   -  person Tudor    schedule 27.03.2012
comment
@Tudor да, в требовании задачи говорилось, что мы должны использовать семафор.   -  person Shawn Lien    schedule 27.03.2012
comment
Использование семафоров для очередей производителей-потребителей является абсолютно стандартным подходом «Информатика 101». Их использование в ОП тоже правильно, но, к сожалению, манипуляции с индексами буфера и неприятный случай преждевременного объединения вызвали проблемы.   -  person Martin James    schedule 27.03.2012
comment
@ShawnLien Я добавил ссылку на свой ответ, которая может быть вам очень полезна. Проверить   -  person Pavan Manjunath    schedule 27.03.2012


Ответы (2)


Размер вашего буфера равен 2. Первые 2 производителя заполняют этот буфер. Следовательно, третий ждет, пока потребитель возьмет один элемент, чтобы он мог добавить его в буфер. Но ваш pthread_join внутри цикла производителя вообще не позволяет создавать потребителей! pthread_join приостанавливает процесс main до тех пор, пока не завершится 3-й производитель. Отсюда тупик, когда в третьем Производитель неопределенно долго ждет освобождения буфера потребителем, который так и не появился.

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

person Pavan Manjunath    schedule 27.03.2012
comment
+1 за катастрофу join(). К сожалению, многие разработчики (и примеры многопоточности), похоже, спешат использовать join() с потоками как можно скорее, независимо от того, может ли какой-либо другой подход избежать опасностей жесткой блокировки join() и подобных генераторов взаимоблокировок. - person Martin James; 27.03.2012
comment
@Martin James Большое спасибо, упражнение действительно полезно. Раньше я также замечал проблему, вызванную _join(), но я не знаю, как исправить свою программу. Я переписал свою программу, основанную на упражнении, и результат выглядит хорошо, но все еще есть некоторые проблемы. Кажется, что поток производителя не спит достаточно долго и снова начинает зацикливаться, из двух моих новых 2 результатов я заметил, что производитель будет продолжать создавать, даже если буферного пространства больше нет. Можете ли вы сказать мне, где я должен исправить мой код? Если есть 3 производителя, производитель должен создавать ровно 3 раза, а не зацикливаться снова и снова. Большое спасибо - person Shawn Lien; 28.03.2012
comment
@ShawnLien Я сейчас очень занят, я вернусь и отредактирую свой ответ, чтобы дать вам дополнительные указатели в коде. - person Pavan Manjunath; 28.03.2012
comment
Я нашел проблему, просто потому, что для цикла создается многопоточность. Я исправил это, теперь он может отображать правильное количество производителя и потребителя. Но я столкнулся с новой проблемой, я не знаю, какую переменную следует использовать для отображения элемента, оставшегося в буфере. Я пробую задний, передний и элемент, но ни один из них не может отображаться правильно. Например, если производитель = 3, потребитель = 1, после завершения программы внутри буфера должно остаться 2 элемента, но если я использую заднюю часть как переменную, она всегда показывает 1 элемент, оставшийся внутри буфера. Я думаю, причина в том, что %buffersize в обоих методах, но я не уверен, что знаю, как это сделать правильно - person Shawn Lien; 28.03.2012

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

Однако ваше использование буфера кажется немного «неправильным». Я предполагаю, что буфер должен быть круглым? Если это так, вы должны установить индекс обратно на 0 всякий раз, когда передний или задний индекс достигает конца массива. Вы должны делать это внутри блокировки мьютекса как в коде производителя, так и в коде потребителя. Проверка индексов должна выполняться исключительно для сброса их в начало буфера и никоим образом не должна изменять работу семафора signal/wait.

Это, и вы, кажется, присоединяетесь к темам, как только они создаются. Обычно для тестирования/демонстрации работы производителя/потребителя потоки не создаются непрерывно, сразу же присоединяются (так что конструирующий поток должен немедленно ждать), а затем оставляются для завершения. Чаще всего запускаются некоторые потоки производителя и потребителя, которые зацикливаются, непрерывно создавая/потребляя сообщения (возможно, с очень коротким циклом сна - такие циклы, которые, по мнению некоторых разработчиков, не имеют допустимого использования и являются анти-шаблоном).

Непрерывное создание/завершение/уничтожение потоков очень расточительно и приведет к тому, что в вашем тестовом коде приложение будет выполнять почти все накладные расходы и очень мало производитель/потребитель.

И последнее: не присоединяйтесь к темам, не подумав об этом секунду или две. Это может быть не оптимально, это может быть не нужно, это может быть помехой, это может быть катастрофой. Если соединение предназначено исключительно для того, чтобы предотвратить преждевременное завершение вашего основного потока (и, следовательно, процесса), может быть лучше найти другой способ - дождаться ввода с клавиатуры или использовать длительный цикл sleep().

person Martin James    schedule 27.03.2012