Thread Synchronization (C++)

std::condition_variable (C++)

Saw this while working on my SLAM for VR Headset project.

The condition_variable is used to wake up threads that are waiting on data.

Resources

Include

#include <condition_variable>

Notify One vs. Notify All

  • notify_one() wakes up only one of the waiting threads and unblock them (more efficient in scenarios where waking up a single thread is sufficient because it avoids unnecessarily waking up multiple threads)
  • notify_all() wakes up all threads waiting on the condition variable. This is necessary in situations where multiple threads may be waiting for the same condition and all need to recheck the condition when it changes.

Source

Good to solve the Producer-Consumer Problem.

cv.wait(lock, [] { return data_ready; });

What happens when cv.wait() is called:

  1. cv.wait releases the lock and BLOCKS on *this (source)
  2. After being woken up (through cv.notify_one() or cv.notify_all() on another thread, cv.wait() checks the condition of second argument (a lambda function returning true or false).
    • If the predicate returns true, the waiting ends, and the thread proceeds with the next steps. Mutex is LOCKED again.
    • If the predicate returns false, the thread goes back to waiting and blocks. Mutex is UNLOCKED.

Why is the mutex needed, isn't the condition variable enough?

No, you need them in conjunction. Imagine a scenario where you have multiple consumers to the same producer. A condition variable is used to make sure that we only consume from the queue when it is non-empty. However, without a mutex, after condition variable unblocks, since you’re not using a mutex, the rest of the code doesn’t guarantee that the queue is not empty. You can have a Race Condition again.

mutex mtx; 
condition_variable cv; 
bool data_ready = false; 
 
void producer() { 
	// Simulate data production 
	this_thread::sleep_for(chrono::seconds(2)); 
	// lock release 
	lock_guard<mutex> lock(mtx); 
	// variable to avoid spurious wakeup 
	data_ready = true; 
	// logging notification to console 
	cout << "Data Produced!" << endl; 
	// notify consumer when done 
	cv.notify_one(); 
} 
 
void consumer()  { 
	// locking 
	unique_lock<mutex> lock(mtx); 
	// waiting 
	cv.wait(lock, [] { return data_ready; });
	cout << "Data consumed!" << endl; 
} 
 
// drive code 
int main()  { 
	thread consumer_thread(consumer); 
	thread producer_thread(producer); 
 
	consumer_thread.join(); 
	producer_thread.join(); 
 
	return 0; 
}

The second argument explained:

Another example

// condition variable and mutex lock 
condition_variable cv; 
mutex m; 
 
// shared resource 
int val = 0; 
 
void add(int num) { 
	lock_guard<mutex> lock(m); 
	val += num; 
	cout << "After addition: " << val << endl; 
	cv.notify_one(); 
} 
 
void sub(int num)  { 
	unique_lock<mutex> ulock(m); 
	cv.wait(ulock, 
			[] { return (val != 0) ? true : false; }); 
	if (val >= num) { 
		val -= num; 
		cout << "After subtraction: " << val << endl; 
	} 
	else { 
		cout << "Cannot Subtract now!" << endl; 
	} 
	cout << "Total number Now: " << val << endl; 
} 
 
// driver code 
int main() 
{ 
	thread t2(sub, 600); 
	thread t1(add, 900); 
	t1.join(); 
	t2.join(); 
	return 0; 
}

notify_one vs. notify_all

https://stackoverflow.com/questions/43759609/stdcondition-variablenotify-all-i-need-an-example