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.
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 anif
statement only might be, depending on the semantics of signaling. Thus, always usewhile
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,. Spurious wakeups are further reason to re-check the condition a thread is waiting on. 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.
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:
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.
cond_t empty, fill;mutex_t mutex;void *producer(void *arg) {int i;for (i = 0; i < loops; i++){Pthread_mutex_lock(&mutex); //p1while (count == MAX) //p2Pthread_cond_wait(&empty, &mutex); //p3put(i); //p4Pthread_cond_signal(&fill); //p5Pthread_mutex_unlock(&mutex); //p6}}void *consumer(void *arg) {int i;for (i = 0; i<loops;i++){Pthread_mutex_lock(&mutex); //c1while (count == 0) //c2Pthread_cond_wait(&fill, &mutex); //c3int tmp = get(); //c4Pthread_cond_signal(&empty); //c5Pthread_mutex_unlock(&mutex); //c6printf("%d\n", tmp);}}
Get hands-on with 1400+ tech skills courses.