Reputation: 1377
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
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
Reputation: 1377
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
Reputation: 180103
In the first place, note that in both programs:
mutexP
, that semaphore serves no useful purpose.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