Please explain the use of condition variables in c++ threads, and why do we need to use `unique_lock` and `mutex` alongwith this
Asked Answered
P

2

7

I am refering to this particular piece of code:

this code basically has three threads 1. Perform some handshaking with server 2. Load Data from XML files. 3. Do processing on data loaded from XML. As we can see that Task 1 is not dependent on any other Tasks but Task 3 is dependent on Task 2. So, it means Task 1 and Task 2 can be run in parallel by different Threads to improve the performance of application. Thus, application is built to be multithreaded.

#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std::placeholders;

class Application
{
  std::mutex m_mutex;
  std::condition_variable m_condVar;
  bool m_bDataLoaded;
public:
  Application()
  {
    m_bDataLoaded = false;
  }
  void loadData()
  {
   // Make This Thread sleep for 1 Second
   std::this_thread::sleep_for(std::chrono::milliseconds(1000));
   std::cout<<"Loading Data from XML"<<std::endl;
   // Lock The Data structure
   std::lock_guard<std::mutex> guard(m_mutex);
   // Set the flag to true, means data is loaded
   m_bDataLoaded = true;
   // Notify the condition variable
   m_condVar.notify_one();
  }
  bool isDataLoaded()
  {
    return m_bDataLoaded;
  }
  void mainTask()
  {
    std::cout<<"Do Some Handshaking"<<std::endl;
    // Acquire the lock
    std::unique_lock<std::mutex> mlock(m_mutex);
    // Start waiting for the Condition Variable to get signaled
    // Wait() will internally release the lock and make the thread to block
    // As soon as condition variable get signaled, resume the thread and
    // again acquire the lock. Then check if condition is met or not
    // If condition is met then continue else again go in wait.
    m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));
    std::cout<<"Do Processing On loaded Data"<<std::endl;
  }
};
int main()
{
   Application app;
   std::thread thread_1(&Application::mainTask, &app);
   std::thread thread_2(&Application::loadData, &app);
   thread_2.join();
   thread_1.join();
   return 0;
}

this code is from http://thispointer.com/c11-multithreading-part-7-condition-variables-explained/

Thanks

Personally answered 14/5, 2018 at 13:10 Comment(12)
the question is broad but you should read en.cppreference.com/w/cpp/thread/condition_variableMultidisciplinary
Unless you're intentionally trying to make this difficult, I'd have the data reader insert the data into a thread safe queue, and have the XML processor read its input from there. The queue includes all the synchronization necessary, so neither the reader nor the processor needs to deal with synchronization directly at all.Uranyl
@JerryCoffin I think it was intentionally made difficult to demonstrate how condition variables are used given that it came from a tutorial site.Ignescent
@JerryCoffin I have already stated that this code is not mine, it's from a tutorial website.Personally
@Multidisciplinary I have already seen the link you mentioned, the thing is it doesn't explain the useunique_lock and meaning of arguements passed to wait()Personally
@Nilesh you can follow link to membre function and read "Atomically releases lock, blocks the current executing thread, and adds it to the list of threads waiting on *this" and you have a section called parametersMultidisciplinary
@TobySpeight yeah go check itPersonally
I couldn't find the licensing information on that page. Could you point us to the exact part that says you're allowed to republish it? Thanks.Tarentarentum
@TobySpeight thispointer.com/terms-and-conditions-policy check this out the website author clearly states information provided is usable, just he doesn't takes liability of it's completeness or of any errors.Personally
I don't see any permission granted: "All content provided on this blog is for informational purposes only. The owner of this blog makes no representations as to the accuracy or completeness of any information on this site or found by following any link on this site. The owner will not be liable for any errors or omissions in this information nor for the availability of this information. The owner will not be liable for any losses, injuries, or damages from the display or use of this information. These terms and conditions of use are subject to change at anytime and without notice."Tarentarentum
@TobySpeight; I don't see any persmission denied in it too, it doesn't specifies anything about content republishing too, you may check with the website author for any info on content republishing. And it is not even republished on stackoverflow I've just reiterated the code for reference, I can redirect you people to page directly but it contains many code snippets, how are you supposed to know which one I'm talking abouPersonally
@TobySpeight: This question reproduces just a code snippet, which is a pretty minimal example showing the use of std::condition_variable. There's legal precedence that copyright doesn't protect the minimal expression of an idea.Tome
P
17

Condition variables allow one to atomically release a held mutex and put the thread to sleep. Then, after being signaled, atomically re-acquire the mutex and wake up. You run into this, for example, in the producer/consumer problem. You will deadlock if you go to sleep while holding the mutex, but you could also deadlock if you release it before sleeping (by missing the signal to wake up).

It's not something that can be explained in a few paragraphs without examples, and there are several well-known pitfalls and caveats to using condition variables. Check out "An Introduction to Programming with Threads" by Andrew D. Birrell.

Regardless of the language, condition variables always take a mutex. The mutex must be held when wait is called. You should always verify that the desired condition is still true after returning from wait. That's why you always see conditional waits wrapped in a while loop. C++11 also gives you the predicate overload, which is syntactic sugar for the while loop.

The mutex protects the shared state. The condition lets you block until signaled.

unique_lock is an RAII (Resource Acquisition Is Initialization) wrapper for locking and unlocking the given mutex. It's conceptually identical to the lock statement in C#. It simplifies exception handling by tying the mutex acquisition and release to the lifetime of the unique_lock instance. I don't know if there's a reason why condition_variable forces you to use it other than the fact that it's good practice. The only difference between unique_lock and lock_guard is that unique_lock can be unlocked... which is why you have to use it instead of lock_guard with condition_variable.

Plataea answered 15/5, 2018 at 10:16 Comment(0)
D
0

Late post; answering

why do we need to use unique_lock and mutex alongwith this

as the accepted answer doesn't.

From this

std::condition_variable works only with std::unique_lockstd::mutex; this restriction allows for maximal efficiency on some platforms.

Durston answered 19/8, 2021 at 6:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.