Laura Corssac
Laura Corssac

Reputation: 1377

Unable to make an Producer-Consumer instance with list in c++

guys. I am learning about the Producer-Consumer Problem. My professor gave us an example code using a classical int array to share resources between the two threads. It works as expected, however, I wanted to try it using std:list class from C++ and it doesn't work as expected. The consumer seems not to respect sem_wait(&full); so it tries to consume many times when there is nothing in the shared list.

Original code with array buffer

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

#define    N 2
#define    TRUE 1

int    buffer[N], in = 0, out = 0;
sem_t  empty, full, mutexC, mutexP;

void *producer(void *arg) {

   while(TRUE) {
       sleep(rand()%5);

       sem_wait(&empty);
       sem_wait(&mutexP);
    
       buffer[in] = rand() % 100;
       printf("Producing buffer[%d] = %d\n", in, buffer[in]);
       in= (in+1) % N;

       sem_post(&mutexP);
       sem_post(&full);
       
   }
}

void *consumer(void *arg) {

   while(TRUE) {
       
       sleep(rand()%5);
       sem_wait(&full);
       sem_wait(&mutexC);
       
       printf("Consuming buffer[%d] = %d\n", out, buffer[out]);
       out = (out+1) % N;
       
       sem_post(&mutexC);
       sem_post(&empty);
       
   }
}

int main(int argc, char *argv[ ]) {
    pthread_t cons, prod;

    sem_init(&mutexC, 0, 1);
    sem_init(&mutexP, 0, 1);
    sem_init(&empty, 0, N);
    sem_init(&full, 0, 0);
    
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    
    pthread_exit(0);
}

My implementation with list:

#include    <pthread.h>
#include    <semaphore.h>
#include    <stdlib.h>
#include    <unistd.h>
#include    <iostream>
#include    <list>

#define    TRUE 1
#define    N 2

using namespace std;

list<int> sharedList;

sem_t  empty, full, mutexC, mutexP;

void *producer(void *arg) {

   while(TRUE) {
       
       sleep(rand()%5);

       sem_wait(&empty);
       sem_wait(&mutexP);
       int prod = rand() % 100;
       cout << "producing: " << prod << endl;
       sharedList.push_back(prod);

       sem_post(&mutexP);
       sem_post(&full);
       
   }
}

void *consumer(void *arg) {

   while(TRUE) {
      
       sleep(rand()%5);

       sem_wait(&full);
       sem_wait(&mutexC);
       
       if (!sharedList.empty()) {
           cout << "consuming: ";
           cout << sharedList.front() << endl;
           sharedList.pop_front();
       } else {
           cout << "not possible to consume" << endl;
       }
       sem_post(&mutexC);
       sem_post(&empty);
   }
}

int main(int argc, char *argv[ ]) {
    pthread_t cons, prod;

    sem_init(&mutexC, 0, 1);
    sem_init(&mutexP, 0, 1);
    sem_init(&empty, 0, N);
    sem_init(&full, 0, 0);
    
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    
    pthread_exit(0);
}

The - unexpected - logs from my implementation:

producing: 73
consuming: 73
not possible to consume
producing: 44
consuming: 44
producing: 9
producing: 65
consuming: 9
producing: 87
consuming: 65
consuming: producing: 29
87
not possible to consume
consuming: 29
producing: 9
producing: 60
consuming: 9
producing: 78

Can somebody explain to me what is happening? Thanks in advance!

Upvotes: 2

Views: 155

Answers (3)

mevets
mevets

Reputation: 10445

rand() % 6 can generate some interesting sequences; so the spurious wakeup not possible to consume is to be expected.

Beyond that, the only confusing bit is:

consuming: producing: 29
87
not possible to consume

Which isn't that confusing; the io statements interleave. If they didn't, you would see this as:

consuming: 87
producing: 29
not possible to consume

where the last statement would be a result of checking the condition just before the producer created 29.

Upvotes: 0

Laura Corssac
Laura Corssac

Reputation: 1377

Follow up

Although the second code does have its errors, it turns out that macOS does ignore sem_wait(). I tried to initialize all the semaphores with 0 expecting the program to enter a deadlock, but it actually didn't. So, if running on macOS, please refer to this answer

Upvotes: 0

John Bollinger
John Bollinger

Reputation: 180103

In the first place, note that in both programs:

  • with only one producer ever contending for semaphore mutexP, that semaphore serves no useful purpose.
  • Likewise, with only one consumer ever contending for semaphore mutexC, that serves no useful purpose either.

Now, consider what purpose is served in your first program by initializing semaphore empty with value 2 instead of value 1: I presume that you will agree that doing so allows the producer to run concurrently with the consumer, one position ahead. This is ok because accesses to different array elements do not conflict.

But when your storage is a std::list instead of an array, it is not ok for two threads to manipulate it concurrently if one of them modifies it. There are several complementary ways to look at it, among them:

  • a std::list is a single object, access to which must be synchronized, whereas an array is merely an aggregate of its elements, not a distinct object of its own as far as synchronization requirements are concerned.

  • a std::list has members -- maintaining the current list size, for example -- that are used by all manipulations, access to which must be synchronized.

However you choose to look at it, you must ensure that the std::list cannot be accessed concurrently by two threads if one of those threads modifies it.

Upvotes: 2

Related Questions