Threads
Introduction
Normally when we run a program we have an instruction that is executed followed by another instruction and so on. This is called a path of execution. With threading we can have more than one path of execution at the same time. How does this work with a single CPU ? The same way a single CPU can run multiple processes by switching among processes. Here the CPU will switch among the threads in a process. However we don't know how much time the CPU will allocate to each thread. That is not determinstic. This comes from the definition of thread.C++ did not have support for threads before C++ and the programmer had to rely on the underlying system. Unix programmers used Unix functions and Windows programmer used the Microsoft SDK API as an example. C++ 11 provides classes and constructs for threads in a uniform manner.
Creating Threads
File: thread1.cpp
#include <iostream> #include <thread> void thread_function() { std::cout << "thread function\n"; } int main() { std::thread t1( &thread_function ); // t starts running std::cout << "main thread\n"; t1.join(); // main thread waits for the thread t to finish return 0; }The above code shows how we can create a thread and run it.
We first define our function that the thread object will run. void thread_function() { cout << "thread function\n"; } Note there is at least always one thread running in a process. We get the second ( child ) thread going with the statment: thread t1( &thread_function ); // t1 starts running cout << "main thread\n"; t1.join(); // main thread waits for the thread t1 to finish Upon definition the thread "t1" starts running. The main thread is also running. So we have 2 threads running at the same time. In the "main" function we have the statement t1.join(); // main thread waits for the thread t1 to finish This blocks the main thread till the "t1" thread finishes. Otherwise the main thread can exit ending the program and the "t1" thread may not have finished. We have many options and not just a standalone function as to what the thread object may run. The below program shows some of these options.
Different ways of creating threads.
File: thread2.cpp
#include <chrono> #include <iostream> #include <thread> #include <utility> using namespace std ; void f1(int n) { for (int i = 0; i < 5; ++i) { cout << "Thread f1 executing\n"; ++n; this_thread::sleep_for(chrono::milliseconds(10)); } } void f2(int& n2) { for (int i1 = 0; i1 < 5; ++i1) { cout << "Thread f2 executing: " << i1 << "\n"; ++n2; this_thread::sleep_for(chrono::milliseconds(10)); } n2 = 101 ; } class foo { public: void bar() { for (int i = 0; i < 5; ++i) { cout << "Thread bar executing\n"; ++n; this_thread::sleep_for(chrono::milliseconds(10)); } } int n = 0; }; class baz { public: void operator()() { for (int i = 0; i < 5; ++i) { cout << "Thread baz executing\n"; ++n; this_thread::sleep_for(chrono::milliseconds(10)); } } int n = 0; }; int main() { int n1 = 0; foo f; baz b; thread t1; // t1 does not run anything thread t2(f1, n1 + 1); // pass by value //Thread 2 thread t3(f2, ref(n1) ); // pass by reference this_thread::sleep_for(chrono::milliseconds(1)); cout << "In main about to shift t3 to t4:" << endl ; thread t4( move(t3) ); // t4 is now running f2(). t3 is no longer a thread thread t5(&foo::bar, &f); //executing a method in a class thread t6(b); //Executing a functor t2.join(); t4.join(); t5.join(); t6.join(); cout << "The value of n1: " << n1 << endl ; }
Output: $ rm a.exe ; g++ thread2.cpp ; ./a.exe Thread f1 executing Thread f2 executing: 0 Thread f2 executing: 1 Thread f1 executing In main about to shift t3 to t4: Thread bar executing Thread baz executing Thread f2 executing: 2 Thread f1 executing Thread f2 executing: 3 Thread f1 executing Thread bar executing Thread baz executing Thread f1 executing Thread f2 executing: 4 Thread baz executing Thread bar executing Thread baz executing Thread bar executing Thread bar executing Thread baz executing The value of n1: 101 Notice that if you run the program multiple times the output is different every time and that's because the amount of time the CPU spends on each thread is not deterministic. std::thread t1; If we don't provide anything to the thread object then nothing is run. This is used in certain cases when we want to create a thread object that can be assigned another thread later on. Ex: t1 = std::thread{ func1, arg1 }; std::thread t2(f1, n1 + 1); // pass by value We are executing the function "f1" and passing it an argument by value. std::thread t3(f2, std::ref(n1)); // pass by reference We need to use "std::ref" to make sure we are passing the variable by reference. If we don't use "ref" then we get a compiler error. At the end of "main" we print out the value of "Ajay" to confirm that it has been changed. this_thread::sleep_for(chrono::milliseconds(1)); cout << "In main about to shift t3 to t4:" << endl ; std::thread t4( std::move(t3) ); // t4 is now running f2(). t3 is no longer a thread In the above we are transferring the "t3" contents to a new thread object "t4" . Execution picks up where we left off in t3. We can see that by having the main thread sleep for some time jus before the shift and check the values of the "f2" function that are printed out. std::thread t5(&foo::bar, &f); //executing a method in a class Here we are executing a method in a class and we need to provide an object of the class as well. std::thread t6(b); In the above we are executing a functor in a thread. Notice that the output is not quite clean as different threads print. The "cout" is not thread safe. In the next section we shall study how we can control this.
Control Access
File: thread3.cpp
#include <iostream> #include <thread> using namespace std ; void thread_function1() { for( int i1=0 ; i1<5 ; i1++ ) cout << "Thread function1: " << i1 << endl ; } void thread_function2() { for( int i1=0 ; i1<5 ; i1++ ) cout << "Thread function2: " << i1 << endl ; } int main() { thread t1( &thread_function1 ); // t1 starts running thread t2( &thread_function2 ); // t2 starts running //cout << "main thread\n"; // main thread waits for the thread t1 to finish t1.join(); t2.join(); return 0; } Output: $ rm a.exe ; g++ thread3.cpp ; ./a.exe Thread function1: Thread function2: 00 Thread function1: Thread function2: 11 Thread function1: Thread function2: 22 Thread function1: Thread function2: 33 Thread function1: Thread function2: 44 We notice that the output from the 2 threads is intermingled. Let's correct this issue by making sure that only one thread executes a "cout" at a time.
Mutex
We have the concepts of a "mutex" . The mutex object has 2 methods "lock()" and "unlock()" . Suppose we have a single mutex object and 2 threads hit the "lock" method at the same time; then only 1 thread will enter the code after the lock. The other thread will be forced to wait till the first thread releases the lock with the "unlock()" call.File: thread4.cpp
#include <iostream> #include <thread> #include <mutex> using namespace std ; //Using a mutex mutex mtx ; void thread_function1() { mtx.lock(); for( int i1=0 ; i1<5 ; i1++ ) cout << "Thread function1: " << i1 << endl ; mtx.unlock(); } void thread_function2() { mtx.lock(); for( int i1=0 ; i1<5 ; i1++ ) cout << "Thread function2: " << i1 << endl ; mtx.unlock(); } int main() { thread t1( &thread_function1 ); // t1 starts running thread t2( &thread_function2 ); // t2 starts running //cout << "main thread\n"; // main thread waits for the thread t1 to finish t1.join(); t2.join(); return 0; } Output: $ rm a.exe ; g++ thread4.cpp ; ./a.exe Thread function1: 0 Thread function1: 1 Thread function1: 2 Thread function1: 3 Thread function1: 4 Thread function2: 0 Thread function2: 1 Thread function2: 2 Thread function2: 3 Thread function2: 4 The output looks much cleaner. So what's going on over here ? thread t1( &thread_function1 ); // t1 starts running The above statement starts to execute the function "thread_function1". The first statement in this function is: mtx.lock(); The "mtx" is a global object and "t1" locks the mutex and starts to execute rest of the instructions in the function. Meanwhile t2 also starts to execute right after t1 started. So t2 will enter "thread_function2()" and execute: mtx.lock(); Since the mutex is loced by "thread_function1()" the t2 thread waits until "thread_function1()" finishes and releases the lock. What if we forget to release the lock in ""thread_function1()" ? Then "thread_function2()" keeps waiting ( forever ) for the lock to be available. We shall see later how "lock_guard" can resolve the problem of forgetting to call "unlock()". In the program we are running "thread_function1()" and then running ""thread_function2()" . We can also use the mutex to make sure that only 1 thread runs their "cout" at a time by placing the mutex just before the "cout" .
File: thread5.cpp
#include <iostream> #include <thread> #include <mutex> using namespace std ; mutex mtx ; void thread_function1() { for( int i1=0 ; i1<5 ; i1++ ) { mtx.lock() ; cout << "Thread function1: " << i1 << endl ; mtx.unlock() ; } } void thread_function2() { for( int i1=0 ; i1<5 ; i1++ ) { mtx.lock() ; cout << "Thread function2: " << i1 << endl ; mtx.unlock() ; } } int main() { thread t1( &thread_function1 ); // t1 starts running thread t2( &thread_function2 ); // t2 starts running //cout << "main thread\n"; // main thread waits for the thread t1 to finish t1.join(); t2.join(); return 0; } $ rm a.exe ; g++ thread5.cpp ; ./a.exe Thread function1: 0 Thread function2: 0 Thread function1: 1 Thread function2: 1 Thread function1: 2 Thread function2: 2 Thread function1: 3 Thread function2: 3 Thread function1: 4 Thread function2: 4
We can tell from the output that the behavior has changed from one thread getting control of the mutex to the threads alternating in printing out their statement. It is also interesting that once a thread releases the lock the other( waiting ) thread gets the mutex. The rule is that if there is a single thread that is waiting then it will obtain the mutex.
Lock Guard
We can use the lock guard to wrap the mutex and the guard will unlock the mutex when it's destructor is called ( goes out of scope ) .File: guard1.cpp
#include <iostream> #include <thread> #include <mutex> using namespace std ; mutex mtx ; void thread_function1() { for( int i1=0 ; i1<5 ; i1++ ) { lock_guard lk( mtx ) ; cout << "Thread function1: " << i1 << endl ; } } void thread_function2() { for( int i1=0 ; i1<5 ; i1++ ) { lock_guard lk( mtx ) ; cout << "Thread function2: " << i1 << endl ; } } int main() { thread t1( &thread_function1 ); // t1 starts running thread t2( &thread_function2 ); // t2 starts running //cout << "main thread\n"; // main thread waits for the thread t1 to finish t1.join(); t2.join(); return 0; } Output: $ rm a.exe ; g++ guard1.cpp ; ./a.exe Thread function1: 0 Thread function2: 0 Thread function1: 1 Thread function2: 1 Thread function1: 2 Thread function2: 2 Thread function1: 3 Thread function2: 3 Thread function1: 4 Thread function2: 4 We wrap the mutex with a guard in the statement: lock_guard lk( mtx ) ; This causes a lock to be placed on the mutex and after printing out the statement the lokc guard object goes out of scope causing the "unlock" method of the mutex to be called.
Unique Lock
A Unique Lock is also a wrapper for mutexes. We will rewrite the "guard1.cpp" to use "unique_lock" instead of the guard.File: unique1.cpp
#include <iostream> #include <thread> #include <mutex> using namespace std ; mutex mtx ; void thread_function1() { for( int i1=0 ; i1<5 ; i1++ ) { unique_lock lk( mtx ) ; cout << "Thread function1: " << i1 << endl ; } } void thread_function2() { for( int i1=0 ; i1<5 ; i1++ ) { unique_lock lk( mtx ) ; cout << "Thread function2: " << i1 << endl ; } } int main() { thread t1( &thread_function1 ); // t1 starts running thread t2( &thread_function2 ); // t2 starts running //cout << "main thread\n"; // main thread waits for the thread t1 to finish t1.join(); t2.join(); return 0; } $ rm a.exe ; g++ unique1.cpp ; ./a.exe Thread function1: 0 Thread function2: 0 Thread function1: 1 Thread function2: 1 Thread function1: 2 Thread function2: 2 Thread function1: 3 Thread function2: 3 Thread function1: 4 Thread function2: 4 The code is almost the same as the one written for "guard1.cpp". So what does "unique_lock" have to offer that "lock_guard" does not have. The unique lock is more flexible to use as it has functions to lock, unlock, recursive locking and can be used with condition variables. We study condition variables in the next section.
Condition
There might be situations where 2 threads need to synchronize and coordinate with each other. It's possible that the first thread does something and then waits. The second thread does something and then signals the first thread. The first thread receives the signal and then resumes. This is where the "condition_variable" comes in. It works hand in hand with the mutex, unique_lock and lock_guard classes.File: cond1.cpp
#include <condition_variable> #include <iostream> #include <mutex> #include <string> #include <thread> using namespace std ; mutex mutexObject ; condition_variable cv; bool ready = false; bool processed = false; void worker_thread() { cout << "Worker thread starting.\n"; // Wait until main() sends signal unique_lock lk( mutexObject ); //ready = true ; //It has locked the lock. Wait needs the lock to be //locked. this_thread::sleep_for(chrono::seconds(5)); //wait releases the lock. Once we get a notify we //will acquire the lock again if it is free. cv.wait(lk, []{ return ready; }); // after the wait, we own the lock. cout << "Worker thread is processing after wait.\n"; cout << "Worker thread signals dtr1 processing completed\n"; // Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); processed = true; cv.notify_one(); } int main() { //worker thread starts and waits for a signal from the //main thread thread worker(worker_thread); { //Let the thread lock first //this_thread::sleep_for(chrono::seconds(5)); lock_guard lk(mutexObject); cout << "main() signals worker thread ready to start.\n"; //Is ready enough to unblock worker thread ? // //this_thread::sleep_for(chrono::seconds(10)); ready = true; cv.notify_one(); //The worker thread cannot proceed at this stage //because the lock has not been freed. After the next //lines the lock guard will go out of scope //this_thread::sleep_for(chrono::seconds(30)); } //cv.notify_one(); // wait for the worker { cout << "Inside main() waiting for the worker thread. Step 1 \n"; unique_lock lk(mutexObject); cout << "Inside main() waiting for the worker thread. Step 2 \n"; //this_thread::sleep_for(chrono::seconds(5)); cv.wait(lk, []{ return processed; }); } cout << "Back in main()" << '\n'; worker.join(); } $ rm a.exe ; g++ cond1.cpp ; ./a.exe main() signals worker thread ready to start. Worker thread starting. Inside main() waiting for the worker thread. Step 1 Worker thread is processing after wait. Worker thread signals dtr1 processing completed Inside main() waiting for the worker thread. Step 2 Back in main()
The above program is written in a way that allows us to experiment and learn about the difference concepts with conditions. The flow is as: main thread worker thread thread worker(worker_thread); Starts the worker sleeps for 5 seconds thread: Waits for signal from the main thread. cv.wait(lk, []{ return ready; }); Signals the worker thread: cv.notify_one(); worker thread wakes up prints cout << "Worker thread is processing after wait.\n"; waits for a signal from the child thread cv.wait(lk, []{ return processed; }); Worker thread signals the main thread. processed = true; cv.notify_one(); Main thread waits for worker thread and exits.
We have the following declarations: mutex mutexObject ; condition_variable cv; bool ready = false; bool processed = false; The worker thread is started and waits for a signal. The "wait" method needs a lock . We lock the mutex using the syntax: unique_lock lk( mutexObject ); Then we pass the lock to the "wait" method. cv.wait(lk, []{ return ready; }); There are 2 variants of the "wait" method. Here we are using the one that takes a predicate. What this is saying is that the predicate has to be true and a signal received then the wait will proceed. The "wait" method also releases the lock on the mutex. This makes sense because another thread might want to lock the same mutex. The main thread executes: lock_guard lk(mutexObject); cout << "main() signals worker thread ready to start.\n"; //Is ready enough to unblock worker thread ? // //this_thread::sleep_for(chrono::seconds(10)); ready = true; cv.notify_one(); We are using a "lock_guard" on the same mutex. We print something and set "ready" to true and then call "notify_one()". However the worker thread will not wake up just yet. We have not released the lock held by the "lock_guard". Once we pass the block then and the lock guard object goes out scope then, the worker thread wakes up. The main thread now waits for the worker thread to finish and signal the main thread. cout << "Inside main() waiting for the worker thread. Step 1 \n"; unique_lock lk(mutexObject); cout << "Inside main() waiting for the worker thread. Step 2 \n"; //this_thread::sleep_for(chrono::seconds(5)); cv.wait(lk, []{ return processed; }); The main thread waits and we are using the predicate again. The documentation for "wait" states that it is a good idea to use "predicate" so that we dan "ignore spurious awakenings" . We need a lock in order to call the "wait" method and we use "unique_lock" for that. Let's examine the output: $ rm a.exe ; g++ cond1.cpp ; ./a.exe 1) main() signals worker thread ready to start. 2) Worker thread starting. 3) Inside main() waiting for the worker thread. Step 1 4) Worker thread is processing after wait. 5) Worker thread signals dtr1 processing completed 6) Inside main() waiting for the worker thread. Step 2 7) Back in main() We have labelled the output statements so that we can refer to them. The main program prints "1)" and starts the "worker thread" and the main program executes: lock_guard lk(mutexObject); This will lock the global object. That means when the worker thread encounters: lock_guard lk(mutexObject); It is going to "hang" because the "locK()" method will encounter the mutex object that was locked by the main thread. How can we validate this ? We will modify our code a bit.
File: cond2.cpp
#include <condition_variable> #include <iostream> #include <mutex> #include <string> #include <thread> using namespace std ; mutex mutexObject ; condition_variable cv; bool ready = false; bool processed = false; void worker_thread() { cout << "Worker thread starting.\n"; // Wait until main() sends signal unique_lock lk( mutexObject ); cout << "worker thread about to acquire the lock.\n" ; //ready = true ; //It has locked the lock. Wait needs the lock to be //locked. this_thread::sleep_for(chrono::seconds(5)); //wait releases the lock. Once we get a notify we //will acquire the lock again if it is free. cv.wait(lk, []{ return ready; }); // after the wait, we own the lock. cout << "Worker thread is processing after wait.\n"; cout << "Worker thread signals dtr1 processing completed\n"; // Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); processed = true; cv.notify_one(); } int main() { //worker thread starts and waits for a signal from the //main thread thread worker(worker_thread); { //Let the thread lock first //this_thread::sleep_for(chrono::seconds(5)); lock_guard lk(mutexObject); cout << "main() signals worker thread ready to start.\n"; //Is ready enough to unblock worker thread ? // //this_thread::sleep_for(chrono::seconds(10)); ready = true; cv.notify_one(); //The worker thread cannot proceed at this stage //because the lock has not been freed. After the next //lines the lock guard will go out of scope this_thread::sleep_for(chrono::seconds(30)); cout << "main() about to release the lock guard.\n" ; } //cv.notify_one(); // wait for the worker { cout << "Inside main() waiting for the worker thread. Step 1 \n"; unique_lock lk(mutexObject); cout << "Inside main() waiting for the worker thread. Step 2 \n"; //this_thread::sleep_for(chrono::seconds(5)); cv.wait(lk, []{ return processed; }); } cout << "Back in main()" << '\n'; worker.join(); } $ rm a.exe ; g++ cond2.cpp ; ./a.exe main() signals worker thread ready to start. Worker thread starting. main() about to release the lock guard. worker thread about to acquire the lock. Inside main() waiting for the worker thread. Step 1 Worker thread is processing after wait. Worker thread signals dtr1 processing completed Inside main() waiting for the worker thread. Step 2 Back in main() Notice the delay after the line Worker thread starting. This is coming from the delay in the lock getting released by the main thread in line: this_thread::sleep_for(chrono::seconds(30)); cout << "main() about to release the lock guard.\n" ; } Only after the main releases the lock do we see the worker thread acquire the lock. main() about to release the lock guard. Inside main() waiting for the worker thread. Step 1 worker thread about to acquire the lock.
File: deadlock.cpp
#include <condition_variable> #include <iostream> #include <mutex> #include <string> #include <thread> using namespace std ; mutex mutexObject ; condition_variable cv; bool ready = false; bool processed = false; void worker_thread() { cout << "Worker thread starting.\n"; // Wait until main() sends signal unique_lock lk( mutexObject ); //ready = true ; //It has locked the lock. Wait needs the lock to be //locked. this_thread::sleep_for(chrono::seconds(5)); //wait releases the lock. Once we get a notify we //will acquire the lock again if it is free. cv.wait(lk, []{ return ready; }); // after the wait, we own the lock. cout << "Worker thread is processing after wait.\n"; cout << "Worker thread signals dtr1 processing completed\n"; // Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); processed = true; cv.notify_one(); } int main() { //worker thread starts and waits for a signal from the //main thread thread worker(worker_thread); { //Let the thread lock first //this_thread::sleep_for(chrono::seconds(5)); lock_guard lk(mutexObject); cout << "main() signals worker thread ready to start.\n"; //Is ready enough to unblock worker thread ? // //this_thread::sleep_for(chrono::seconds(10)); //ready = true; cv.notify_one(); //The worker thread cannot proceed at this stage //because the lock has not been freed. After the next //lines the lock guard will go out of scope //this_thread::sleep_for(chrono::seconds(30)); } //cv.notify_one(); // wait for the worker { cout << "Inside main() waiting for the worker thread. Step 1 \n"; unique_lock lk(mutexObject); cout << "Inside main() waiting for the worker thread. Step 2 \n"; //this_thread::sleep_for(chrono::seconds(5)); cv.wait(lk, []{ return processed; }); } cout << "Back in main()" << '\n'; worker.join(); } $ rm a.exe ; g++ deadlock.cpp ; ./a.exe main() signals worker thread ready to start. Worker thread starting. Inside main() waiting for the worker thread. Step 1 Inside main() waiting for the worker thread. Step 2 The above code is same as "cond1.cpp" except that we have taken out the statement : //ready = true; The worker thread is waiting for the signal from the main thread. The main thread sends the signal but since the "ready" is still set to false; the wait in the worker thread does not proceed. The main thread proceeds and hit's it's own wait statement. Now we have 2 threads that cannot proceed any further leading to a deadlock. Using threads in a program introduces it's own challenges and we must be careful that our understanding of thread concepts and our program is complete.
Buffer Reader/Writer problem.This is the classic problem where we have a buffer and threads that are writing and reading the buffer. There are 2 types of threads: writer and reader. If the reader thread tries to read a value from the buffer and the buffer is empty then the reader thread waits till something is written to the buffer. If the writer thread tries to write something to the buffer and the buffer is full then the writer thread waits till something is read from the buffer. If the reader thread encountered an empty buffer and the writer thread wrote something to it then it should notify the reader thread to go ahead and read.
File: reader_writer.cpp
#include <condition_variable> #include <iostream> #include <mutex> #include <string> #include <thread> #include <unistd.h> using namespace std ; std::mutex mutexObject1 ; std::mutex mutexObject2 ; std::mutex mtxG ; // mutex for critical section mutex mtxW ; class buffer { public: int* holder ; int counter ; int length_of_buffer ; std::condition_variable cv1 ; std::condition_variable cv2 ; buffer( int buffersize ) { cout << "Buffer costructor with size:" + buffersize << endl ; counter = -1 ; //counter is empty length_of_buffer = buffersize ; //if counter == length_of_buffer-1 means full holder = new int[buffersize] ; } int read( int threadId ) { int retValue = 0 ; //cout << "Start of bufferObject:read() 1: " << endl ; //lockObject1.lock(); std::unique_lock lockObject1(mutexObject1) ; //cout << "Start of bufferObject:read() 2: " << endl ; try { //check if empty while ( counter == -1 ) { cout << "bufferObject:read(): " << " Waiting for something to be written. threadId :" << threadId << " \r\n" ; cv1.wait( lockObject1 ); } //Need to protect this section mtxG.lock() ; int ret ; retValue = holder[counter] ; counter-- ; mtxG.unlock() ; //if ( counter == -1 ) // ready1 = false ; cout <<"Read value:" << retValue << " by thread:" << threadId << " counter" << counter << "\r\n" ; //lockObject2.unlock(); cv2.notify_one(); } catch(exception except ) { cout << "except.toString()" << endl ; } return retValue ; } //-------------------------------------------------------------------------------------- void write( int val1 , int threadId) { std::unique_lock lockObject2( mutexObject2 ) ; try { //check if full while ( counter == (length_of_buffer-1) ) { cout << "bufferObject:write(): Buffer full...Waiting to be read.:" << "Thread.currentThread() threadId :" << threadId << "\r\n" ; //cv2.wait(lockObject2, []{ return ready2; }); cv2.wait( lockObject2 ); } mtxG.lock() ; counter++ ; holder[counter] = val1 ; mtxG.unlock() ; cout <<"Adding value:" << val1 << " by thread:" << threadId << " Thread.currentThread().getName() counter:" << counter << endl ; cv1.notify_one(); lockObject2.unlock() ; } catch( exception e ) { cout << e.what() << endl ; } } } ; //----------------------------------------------------------- class writer { public: buffer& bufferObject ; int threadId ; int interval ; static int value ; writer( buffer& bufferObjectP, int intervalP , int threadIdP ) : bufferObject ( bufferObjectP ), threadId( threadIdP ) { interval = intervalP ; } void run() { try { while ( true ) { mtxW.lock() ; bufferObject.write( value , threadId ) ; value++ ; mtxW.unlock() ; sleep( interval ) ; } } catch( exception e ) { cout << "e.toString()" << endl ; } } } ; int writer::value = 1; class reader { public: buffer& bufferObject ; int interval ; int threadId ; reader( buffer& bufferObjectP, int intervalP, int threadIdP ) : bufferObject ( bufferObjectP ) , threadId( threadIdP ) { interval = intervalP ; } void run() { try { int value ; while ( true ) { value = bufferObject.read( threadId ) ; sleep( interval ) ; } } catch( exception e ) { cout << "read e.toString()" << endl ; } } }; //---------------------------------------------------- int main() { buffer bufferObject( 2 ) ; writer writerObject( bufferObject , 10 , 1 ) ; writer writerObject1( bufferObject , 3 , 2 ) ; reader readerObject( bufferObject , 5 , 1 ) ; reader readerObject1( bufferObject , 5 , 2 ) ; std::thread worker1 ( &writer::run, &writerObject ); std::thread worker4 ( &writer::run, &writerObject1 ); std::thread worker2 ( &reader::run, &readerObject ); std::thread worker3 ( &reader::run, &readerObject1 ); worker1.join() ; worker2.join() ; worker3.join() ; worker4.join() ; return 0 ; } $ rm a.exe ; g++ reader_writer.cpp ; ./a.exe ffer costructor with size: Adding value:1 by thread:1 Thread.currentThread().getName() counter:0 Adding value:2 by thread:2 Thread.currentThread().getName() counter:1 Read value:2 by thread:1 counter0 Read value:1 by thread:2 counter-1 Adding value:3 by thread:2 Thread.currentThread().getName() counter:0 Read value:3 by thread:2 counter-1 bufferObject:read(): Waiting for something to be written. threadId :1 Adding value:4 by thread:2 Thread.currentThread().getName() counter:0 Read value:4 by thread:1 counter-1