C ++:使用`std :: lock_guard`的互斥量足以同步两个`std :: thread`s?

时间:2017-04-10 10:10:08

标签: c++ multithreading c++11 lambda

我的问题是基于下面的C ++代码示例

#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;
}

这是一个让我理解清晰的问题。如果需要,请获取std::condition_variable的示例用法。

我有2个C ++ std::threadmain方法启动。它是osx上的控制台应用程序。所以用clang编译它。两个线程都使用共同的对象 ClassUtility调用方法做一些繁重的任务。对于这个示例代码来解释这种情况,两个线程都运行一个无限循环&amp;只有当时关闭 应用程序关闭,即当我在控制台上按ctrl+c时。

寻求了解:

如果我使用std::lock_guard上的std::mutex来同步或保护对common_obejct ClassUtility的调用,这是否正确?不知怎的,我好像 用这个&#34; 只是一个互斥方法&#34;来解决问题。如果我使用互斥锁锁定循环,则没有任何线程启动。而且,我有时会遇到段错误。这是因为他们是lambdas吗? 分配给每个帖子?

在2个线程或lambdas之间使用std::condition_variable更好地发出信号&amp;同步他们?如果是,那么std::condition_variable将如何使用 在lambdas之间?

注意:由于问题仅在于寻求信息,因此此处提供的代码可能无法编译。它只是提供一个真实的场景

2 个答案:

答案 0 :(得分:8)

您的代码是安全的

请记住,lock_guard只调用.lock()并将.unlock()的调用注入到块的末尾。所以

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

基本上相当于:

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

除了:

  1. 即使通过例外和
  2. 保留了块,也会调用解锁
  3. 确保您不会忘记给它打电话。
  4. 您的代码不是并行

    您在每个线程中相互排除所有循环体。没有什么可以让两个线程实际上并行进行。使用线程的要点是,每个对象可以处理单独的对象集(并且只读取公共对象),因此不必锁定它们。

    在示例代码中,您确实应该仅锁定 对公共对象的工作; std::cout本身就是线程安全的。所以:

    { 
        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.
        }
    }
    

    我认为您尝试编写的实际代码确实有并行实际执行的操作;只是要记住一件事。

    不需要条件变量

    条件变量适用于需要一个线程等待另一个线程执行某些特定操作的情况。在这里,您只需要确保两个线程不会同时修改对象,并且mutex足够且适当。

答案 1 :(得分:1)

除了我的错误之外,你的代码永远不会终止。

正如其他人所指出的那样,由于互斥锁被锁定在睡眠线上会导致长时间的睡眠,因此几乎没有提供并行机会。

这是一个简单的版本,它通过在循环上设置任意有限的限制来终止。

您是否可能不了解join()的作用? 它是当前线程(执行join())直到连接的线程结束。但如果它没有结束当前的线程。

#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 (int i=0;i<10;++i) {

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

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


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

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

      for (int i=0;i<10;++i) {

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

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


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

  return 0;
}