Home C++ Introduction Decisions Loops Input/Output Functions Stack and Heap References Arrays Searching and Sorting Recursion Pointers Character and Strings Structures Classes Inheritance Exceptions Templatess STL Modern C++ Misc Books ----

Threads


Contents

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