Condition variables are a very elegant way to do some synchronisation. A thread can wait for a condition becoming true
to continue and a thread can also make a condition come true
. Since a thread is an active part, it can wait for or make a condition which are others waiting for.
Let’s have a look at an example (see figure 1 below). Thread X creates an item and stores it. Thread Y is waiting for the item to process it. Now let’s say: “thread X is making a condition for thread Y to get to work.”
We will have a look at an implementation of this example below.
This is the entire source code of the example. For a walkthrough, please read below.
The following program is a very simple producer-consumer demo. It shows two threads, one that produces some numbers, the second that consumes them. The demo is simple because we only use one place as buffer between those threads. So every time the producer has an item, it has to wait for the consumer to take it. To tell the consumer (sending a signal), that an item is ready we use the condition variable.
As soon as the ten items are produced and consumed, the threads will stop and the demo will end.
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mtx;
pthread_cond_t cond;
int how_many = 10;
int pool = 0;
void * producer(void * ptr)
{
while (how_many > 0)
{
pthread_mutex_lock(&mtx);
printf("producer: %d\n", how_many);
pool = how_many;
how_many--;
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
}
pthread_exit(0);
}
void * consumer(void * ptr)
{
while (how_many > 0)
{
pthread_mutex_lock(&mtx);
pthread_cond_wait(&cond, &mtx);
printf("consumer: %d\n", pool);
pool = 0;
pthread_mutex_unlock(&mtx);
}
pthread_exit(0);
}
int main(int argc, char ** argv)
{
pthread_t prod, cons;
pthread_mutex_init(&mtx, 0);
pthread_cond_init(&cond, 0);
pthread_create(&cons, 0, consumer, 0);
pthread_create(&prod, 0, producer, 0);
pthread_join(prod, 0);
pthread_join(cons, 0);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mtx);
return 0;
}
The first part is plain simple header inclusion.
Next we have to define some global variables. Global variables are not my favorite, but they will do for this example program. My usually advise: Do not use global data! Ok, the data we need is a mutex and a condition variable.
We also need some variables which take data for the simple producer-consumer demo.
Next we’ll have a look at the consumer:
void * producer(void * ptr)
{
while (how_many > 0)
{
pthread_mutex_lock(&mtx);
printf("producer: %d\n", how_many);
pool = how_many;
how_many--;
pthread_mutex_unlock(&mtx);
Nothing special so far. The thread locks the mutex to enter the critical section, prints out which item has been produced, stores it in the pool and decreases the total number of item to produce in the future. The last step is to unlock the mutex; to exit the critical section.
The new thing we’re looking at in this tutorial is the condition variable and their handling. So far we didn’t really used the condition variable at all, but now we have to send a signal that the pool is full:
What now follows is the usual thread termination statement.
Let’s have a look at the consumer.
So far, there’s nothing special, locking the mutex to enter the critical section is standard procedure. Now appears the question, what if there aren’t any produced items yet? Here comes the answer: the condition variable that we are waiting for.
This statement will unlock the mutex until the condition gets true, say a signal has been sent. Then the mutex gets locked again (using pthread_mutex_lock
, so no timeout), and the thread continues into the critical section.
The rest of the function is quite normal: printing the data from the pool, resetting the pool and unlocking the mutex, exiting the critical section.
Last but not least, standard procedure to terminate the thread.
The function main is quite normal too. The only difference are the initialisation and the destruction of the condition variable.
Please note: the consumer thread is started first, so it can start to consume as soon as there are items ready. This is a measure of quality, not to run the producer first.
int main(int argc, char ** argv)
{
pthread_t prod, cons;
pthread_mutex_init(&mtx, 0);
pthread_cond_init(&cond, 0);
pthread_create(&cons, 0, consumer, 0);
pthread_create(&prod, 0, producer, 0);
pthread_join(prod, 0);
pthread_join(cons, 0);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mtx);
return 0;
}
Multithreading is fun, isn’t it? Go ahead try to implement a producer-consumer system:
a
: using a circular buffer (queue) with more than one spaceb
: using more than one producerc
: using more than one consumerd
: using a queue larger than 1, using multiple producers and consumersThe solutions provided here are not the only one possible implementations. The are many solutions. This ones should give you a hint or if you like to compare your solution with this ones.
a
: thread3a.cb
: not available yetc
: thread3c.cd
: thread3d.cWalkthroughs are not provided by this site. Try to read them on your own.
All source code files provided by this page is free to copy, modify, redistribute and use for any purpose. Use them on your own risk. The tutorial is copyrighted by Mario Konrad.