The Single Buffer Producer/Consumer Solution

This lesson fixes the problem we saw in the solution in the last lesson.

We'll cover the following

As we discussed in the last lesson, we clearly need to have directed signalling i.e. a consumer should only wake up a producer and vice versa. The solution here is once again a small one: use two condition variables, instead of one, in order to properly signal which type of thread should wake up when the state of the system changes. The code excerpt below shows the resulting code.

Press + to interact
cond_t empty, fill;
mutex_t mutex;
void *producer(void *arg) {
int i;
for(i=0;i<loops;i++){
Pthread_mutex_lock(&mutex);
while (count == 1)
Pthread_cond_wait(&empty, &mutex);
put(i);
Pthread_cond_signal(&fill);
Pthread_mutex_unlock(&mutex);
}
}
void *consumer(void *arg) {
int i;
for (i=0;i<loops;i++){
Pthread_mutex_lock(&mutex);
while (count == 0)
Pthread_cond_wait(&fill, &mutex);
int tmp = get();
Pthread_cond_signal(&empty);
Pthread_mutex_unlock(&mutex);
printf("%d\n", tmp);
}
}

In the code, producer threads wait on the condition empty, and signals fill. Conversely, consumer threads wait on fill and signal empty. By doing so, the second problem discussed in a previous lesson is avoided by design: a consumer can never accidentally wake a consumer, and a producer can never accidentally wake a producer.

TIP: USE WHILE (NOT IF) FOR CONDITIONS

When checking for a condition in a multi-threaded program, using a while loop is always correct; using an if statement only might be, depending on the semantics of signaling. Thus, always use while and your code will behave as expected. Using while loops around conditional checks also handles the case where spurious wakeups occur. In some thread packages, due to details of the implementation, it is possible that two threads get woken up though just a single signal has taken place“Pthread cond signal Man Page” by Mysterious author. March, 2011. Available online: http://linux.die.net/man/3/pthread cond signal. The Linux man page shows a nice simple example of why a thread might get a spurious wakeup, due to race conditions within the signal/wakeup code.. Spurious wakeups are further reason to re-check the condition a thread is waiting on.

The correct producer/consumer solution

We now have a working producer/consumer solution, albeit not a fully general one. The last change we make is to enable more concurrency and efficiency; specifically, we add more buffer slots, so that multiple values can be produced before sleeping. Similarly, multiple values can be consumed before sleeping. With just a single producer and consumer, this approach is more efficient as it reduces context switches; with multiple producers or consumers (or both), it even allows concurrent producing or consuming to take place, thus increasing concurrency. Fortunately, it is a small change from our current solution.

The first change for this correct solution is within the buffer structure itself and the corresponding put() and get(). See the code excerpt below:

Press + to interact
int buffer[MAX];
int fill_ptr = 0;
int use_ptr = 0;
int count = 0;
void put (int) value) {
buffer[fill_ptr] = value;
fill_ptr = (fill_ptr + 1) % MAX;
count++;
}
int get() {
int tmp = buffer[use_ptr];
use_ptr = (use_ptr + 1) % MAX;
count--;
return tmp;
}

We also slightly change the conditions that producers and consumers check in order to determine whether to sleep or not. We also show the correct waiting and signaling logic (see the code given below). A producer only sleeps if all buffers are currently filled (p2); similarly, a consumer only sleeps if all buffers are currently empty (c2). And thus we solve the producer/consumer problem; time to sit back and drink a cold one.

Press + to interact
cond_t empty, fill;
mutex_t mutex;
void *producer(void *arg) {
int i;
for (i = 0; i < loops; i++){
Pthread_mutex_lock(&mutex); //p1
while (count == MAX) //p2
Pthread_cond_wait(&empty, &mutex); //p3
put(i); //p4
Pthread_cond_signal(&fill); //p5
Pthread_mutex_unlock(&mutex); //p6
}
}
void *consumer(void *arg) {
int i;
for (i = 0; i<loops;i++){
Pthread_mutex_lock(&mutex); //c1
while (count == 0) //c2
Pthread_cond_wait(&fill, &mutex); //c3
int tmp = get(); //c4
Pthread_cond_signal(&empty); //c5
Pthread_mutex_unlock(&mutex); //c6
printf("%d\n", tmp);
}
}

Get hands-on with 1400+ tech skills courses.