C++ || Snippet – Multi-Process Synchronization Producer Consumer Problem Using Threads
The following is sample code which demonstrates the use of POSIX threads (pthreads), aswell as pthread mutex and condition variables for use on Unix based systems.
The use of mutual exclusion (mutex) variables refers to the requirement of ensuring that no two processes or threads are in their critical section at the same time. Here, a critical section refers to the period of time in which a process accesses a shared resource, such as shared memory. This procedure highlights a classic example of a multi-process synchronization problem known as the producer–consumer problem.
The producer–consumer problem describes a scenario in which two processes (the producer and the consumer) share a common resource (i.e: a string buffer). In this scenario, the producer’s job is to generate a piece of data, update that data with the shared resource (the buffer), and repeat. At the same time, the consumer is consuming that shared resource (i.e: removing something from that same buffer) one piece at a time. The problem is to make sure that the producer wont try to manipulate that shared resource (i.e: add data into the buffer) while the consumer is accessing it; and that the consumer wont try to remove data from that shared resource (the buffer) while the producer is updating it.
In this instance, a solution for the producer can be to go to sleep if a synchronization conflict occurs, and the next time the consumer removes an item from the buffer, it can notify the producer to wake up and start to fill the buffer again. In the same way, the consumer can go to sleep when the producer is accessing it. The next time the producer puts data into the buffer, it wakes up the sleeping consumer and the process continues.
The example on this page demonstrates the use of the above solution using the “pthread_mutex_lock()” function call to limit access to the critical section, and the “pthread_cond_wait()” function call to simulate the sleeping process.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
// ============================================================================ // Author: Kenneth Perkins // Date: Oct 3, 2013 // Taken From: http://programmingnotes.org/ // File: Condvar.cpp // Description: Demonstrate the use of the producer consumer problem // using mutex and condition variables // ============================================================================ #include <iostream> #include <cstdlib> #include <pthread.h> using namespace std; // Compile & Run // g++ Condvar.cpp -lpthread -o Condvar // ./Condvar // function prototypes void* Produce(void* arg); void* Consume(void* arg); // global variables // the mutex and the condition variable pthread_mutex_t mutex; pthread_cond_t cond; // the condition flag bool condition = false; // the item produced int count = 0; // how much to produce const int NUM_TO_PRODUCE = 6; int main() { // declare variables pthread_t producerThread; pthread_t consumerThread; // initialize the mutex and the condition variable pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); cout <<"\nThe Parent is creating the producer and consumer threads..\n" << endl; // create a producer thread if(pthread_create(&producerThread, NULL, Produce, NULL) < 0) { perror("pthread_create"); exit(1); } // create a consumer thread if(pthread_create(&consumerThread, NULL, Consume, NULL) < 0) { perror("pthread_create"); exit(1); } // wait for the threads to complete // similar to the wait() system call for fork() processes if(pthread_join(producerThread, NULL) < 0) { perror("pthread_join"); exit(1); } if(pthread_join(consumerThread, NULL) < 0) { perror("pthread_join"); exit(1); } cout <<"\nBoth threads have completed and have terminated!\n" <<"\nThe Parent is now exiting...\n"; return 0; }// end of main /** * The producer thread function * @param arg - pointer to the thread local data - unused */ void* Produce(void* arg) { // produce things until the loop condition is met while(count < NUM_TO_PRODUCE) { // lock the mutex to protect the condition variable if(pthread_mutex_lock(&mutex) < 0) { perror("pthread_mutex_lock"); exit(1); } // we have produced something that has not been // consumed yet, so we sleep (wait) until the consumer // wakes us up. while(condition) { // sleep (wait) on a condition variable until the // the consumer wakes the producer up. if(pthread_cond_wait(&cond, &mutex) < 0) { perror("pthread_cond_wait"); exit(1); } } // produce an item cout <<"Produced: "<<++count<<endl; // we have produced something condition = true; // wake up the sleeping consumer if(pthread_cond_signal(&cond) < 0) { perror("pthread_cond_signal"); exit(1); } // release the mutex lock if(pthread_mutex_unlock(&mutex) < 0) { perror("pthread_mutex_unlock"); exit(1); } } return 0; }// end of Produce /** * The consumer function * @param arg - pointer to the thread local data - unused */ void* Consume(void* arg) { // consume things until the loop condition is met while(count < NUM_TO_PRODUCE) { // lock the mutex to protect the condition variable if(pthread_mutex_lock(&mutex) < 0) { perror("pthread_mutex_lock"); exit(1); } // if there is nothing to consume, then sleep while(!condition) { // sleep (wait) on a condition variable until the // producer wakes the consumer up. if(pthread_cond_wait(&cond, &mutex) < 0) { perror("pthread_cond_wait"); exit(1); } } cout <<"Consumed: "<<count<<endl<<endl; // consume the item condition = false; // wake up the sleeping producer if(pthread_cond_signal(&cond) < 0) { perror("pthread_cond_signal"); exit(1); } // unlock the mutex if(pthread_mutex_unlock(&mutex) < 0) { perror("pthread_mutex_unlock"); exit(1); } } return 0; }// http://programmingnotes.org/ |
QUICK NOTES:
The highlighted lines are sections of interest to look out for.
To use pthreads, the compiler flag “-lpthread” must be set.
The code is heavily commented, so no further insight is necessary. If you have any questions, feel free to leave a comment below.
The following is sample output:
The Parent is creating the producer and consumer threads..
Produced: 1
Consumed: 1Produced: 2
Consumed: 2Produced: 3
Consumed: 3Produced: 4
Consumed: 4Produced: 5
Consumed: 5Produced: 6
Consumed: 6Both threads have completed and have terminated!
The Parent is now exiting...
This example’s use of a shared resource is a form of cheating based upon the classical description of the producer consumer model. In this version the only shared resource is stdout. The consumer never reads data produced by the consumer. This example performs thread synchronization without actually sharing any data.
Hi, thanks for visiting!
There is a shared resource in this example. It can be found declared on line 31, the variable named ‘count’.
The shared resource can be found being produced on line 113. Here the variable is incremented and displayed to stdout.
The shared resource can be found being consumed on line 163. Here the variable is simply displayed to stdout.
It’s important to note that this example is meant to demonstrate basic usage of the concept while also being as simple as possible.
Hope this helps!