top of page

Condition Variable: What is the problem with Mutex alone?

Updated: May 1, 2020

Welcome folks,

“If you are waiting for the Right Time, it's now.”


So let's start it because this is the right time.

If you have gone through my last article “Thread: How can I create a thread?”, I recommend to go through this article before continuing here, you know how to synchronize multiple threads to access a shared resource. That program for printing even-odd number is working fine.

But there is a problem.


Let's assume instead of int shared in the last program we have a queue(vector<int> que) which is shared between multiple threads. Let's say one producer thread(t1) is responsible for populating data and pushing into a queue. There are other consumer threads(I am considering single thread for simplicity, thread t2) continually reading from this queue and processing on the produced data.


Now for some reason, producer thread t1 stopped producing data. Now all consumer threads(in our case t2) will keep checking for the data in the queue and acquire a lock. At some point when there is no data in the queue, consumer threads will continue to check for data in the queue.

Now the problem is that there is no data but all consumer threads are checking for data and consuming CPU cycles. If there are 2-3 threads, it will not impact but assume we have an application with 1000s of consumer threads or maybe 10k consumer threads. Now this polling will waste a significant amount of CPU cycles.


So what is the solution?


As the title suggests, Condition Variable.

Condition variables provide yet another way for threads to synchronize. While mutex implements synchronization by controlling thread access to data, condition variables allow threads to synchronize based upon the actual value of data.

Without condition variables, the programmer would need to have threads continually polling (possibly in a critical section), to check if the condition is met. This can be very resource-consuming since the thread would be continuously busy in this activity. A condition variable is a way to achieve the same goal without polling.

A condition variable is always used in conjunction with a mutex lock.


Let's try to simulate this mutex problem and solve it using the condition variable.

int fun1(){
std::this_thread::sleep_for(std::chrono::seconds(2));
while(count < 100){
cout<<"Fun1: count: "<<count<<endl;
count++;
unique_lock<mutex> lock(mutObj);
que.push_back(count);}}


In fun1() we are populating a queue till count < 100. I don’t want to run this thread infinite. I want you to analyze the output prints.

int fun2(){
unique_lock<mutex> lock1(mutObj);
while((count < 100) || (que.size() > 0)){
cout<<"fun2: que size: "<< que.size()<<endl;
for(autoitr : que){
if(que.size() > 0){
cout<<"Count: "<<itr<<endl;
que.pop_back();}}}}

After running this code, you will observe that while thread t1 is in sleep for 2 seconds but still thread t2 is running and “fun2:que size: 0” is printing and iterating on 'que'. Thread t2 is still consuming CPU cycles. If there are more threads, you can imagine the performance impact.

Now, let's uncomment line number 23 in code.

CondVar.notify_all();

Let's also uncomment line 31, 32, 33.

if((que.size() == 0)){
CondVar.wait(lock1);}

Now while running this code, you will observe that thread t2 is waiting for 2 seconds to notification from thread t1.

What happens here is, thread t2 is waiting for a signal from thread t1 after completion of its job.

For more clarity, you can check CPU usages in both cases.


Important std::condition_variable functions:

  • Wait(): It makes the current thread to block until the condition variable get signalled or a spurious wakeup happens.

  • notify_one(): If any threads are waiting on the same conditional variable object then notify_one unblocks one of the waiting threads.

  • notify_all(): If any threads are waiting on the same conditional variable object then notify_all unblocks all of the waiting threads.

So, folks, this is a brief about condition variable, more importantly why condition variable is required with a mutex.

One more thing, You will observer, I have used unique_lock<mutex> several times. This is a fundamental and useful concept which I will discuss in my next blog.

Comments


© 2023 by Dheeraj Jha

bottom of page