Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Is a mutex with `std::lock_guard` enough to synchronize two `std::thread`s?

My question is based on below sample of C++ code

#include <chrono>
#include <thread>
#include <mutex>
#include <iostream>

class ClassUtility
{
public:
    ClassUtility() {}
    ~ClassUtility() {}

    void do_something() {
      std::cout << "do something called" << std::endl;

      using namespace std::chrono_literals;
      std::this_thread::sleep_for(1s);
    }
};


int main (int argc, const char* argv[]) {

  ClassUtility g_common_object;

  std::mutex  g_mutex;

  std::thread worker_thread_1([&](){

      std::cout << "worker_thread_1 started" << std::endl;

      for (;;) {

          std::lock_guard<std::mutex> lock(g_mutex);

          std::cout << "worker_thread_1 looping" << std::endl;
          g_common_object.do_something();
      }
  });


  std::thread worker_thread_2([&](){

      std::cout << "worker_thread_2 started" << std::endl;

      for (;;) {

          std::lock_guard<std::mutex> lock(g_mutex);

          std::cout << "worker_thread_2 looping" << std::endl;
          g_common_object.do_something();
      }
  });


  worker_thread_1.join();
  worker_thread_2.join();

  return 0;
}

This is more of a question to get my understanding clear rather & get a sample usage of std::condition_variable iff required.

I have 2 C++ std::threads which start up in main method. Its a console app on osx. So compiling it using clang. Both the threads use a common object of ClassUtility to call a method do some heavy task. For this sample code to explain the situation, both the threads run an infinite loop & close down only when the app closes i.e. when I press ctrl+c on the console.

Seek to know:

Is it correct if I jus use a std::lock_guard on std::mutex to synchronize or protect the calls made to the common_obejct of ClassUtility. Somehow, I seem to be getting into trouble with this "just a mutex approach". None of the threads start if I lock gaurd the loops using mutex. Moreover, I get segfaults sometimes. Is this because they are lambdas ? assigned to each thread ?

Is it better to use a std::condition_variable between the 2 threads or lambdas to signal & synchronize them ? If yes, then how would the std::condition_variable be used here between the lambdas ?

Note: As the question is only to seek information, hence the code provided here might not compile. It is just to provide a real scenario

like image 694
TheWaterProgrammer Avatar asked Jan 04 '23 05:01

TheWaterProgrammer


1 Answers

Your code is safe

Remember, the lock_guard just calls .lock() and injects call to .unlock() to the end of the block. So

{
    std::lock_guard<std::mutex> lock(g_mutex);
    std::cout << "worker_thread_1 looping" << std::endl;
    g_common_object.do_something();
}

is basically equivalent to:

{ 
    g_mutex.lock();
    std::cout << "worker_thread_1 looping" << std::endl;
    g_common_object.do_something();
    g_mutex.unlock();
}

except:

  1. the unlock is called even if the block is left via exception and
  2. it ensures you won't forget to call it.

Your code is not parallel

You are mutually excluding all of the loop body in each thread. There is nothing left that both threads could be actually doing in parallel. The main point of using threads is when each can work on separate set of objects (and only read common objects), so they don't have to be locked.

In the example code, you really should be locking only the work on common object; std::cout is thread-safe on it's own. So:

{ 
    std::cout << "worker_thread_1 looping" << std::endl;
    {
        std::lock_guard<std::mutex> lock(g_mutex);
        g_common_object.do_something();
        // unlocks here, because lock_guard injects unlock at the end of innermost scope.
    }
}

I suppose the actual code you are trying to write does have something to actually do in parallel; just a thing to keep in mind.

Condition variables are not needed

Condition variables are for when you need one thread to wait until another thread does some specific thing. Here you are just making sure the two threads are not modifying the object at the same time and for that mutex is sufficient and appropriate.

like image 80
Jan Hudec Avatar answered Jan 13 '23 11:01

Jan Hudec