【问题标题】:C++: Is a mutex with `std::lock_guard` enough to synchronize two `std::thread`s?C++:带有`std::lock_guard`的互斥锁是否足以同步两个`std::thread`?
【发布时间】:2017-04-10 10:10:07
【问题描述】:

我的问题是基于下面的 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::threads 以 main 方法启动。它是osx 上的控制台应用程序。所以使用clang编译它。两个线程都使用一个共同的对象 ClassUtility 调用方法做一些繁重的任务。对于此示例代码来解释这种情况,两个线程都运行一个无限循环并仅在以下情况下关闭 应用程序关闭,即当我在控制台上按 ctrl+c 时。

寻求了解:

如果我在std::mutex 上使用std::lock_guard 来同步或保护对ClassUtilitycommon_obejct 的调用是否正确。不知怎的,我似乎 遇到这种“只是一种互斥方法”的麻烦。如果我使用互斥锁锁定循环,则不会启动任何线程。此外,我有时会遇到段错误。这是因为它们是 lambdas 吗? 分配给每个线程?

在 2 个线程或 lambdas 之间使用std::condition_variable 来发出信号并同步它们会更好吗?如果是,那么如何使用std::condition_variable 在 lambda 之间?

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

【问题讨论】:

  • 这应该可以正常工作。你怎么知道“没有线程启动”?你能通过独立于共享状态的东西来测试它吗(例如每个线程写入自己的文件而不是共享std::cout)?
  • 另外,请注意,您在这里留给并行(甚至线程切换)的空间非常小,因为互斥锁在睡眠期间保持锁定状态。
  • 如果你的代码会出现死锁、饥饿或段错误,那你到底为什么要发布没有的简化代码?
  • @Angew 我放了couts,证明我的线程方法被阻塞了
  • 正如我所提到的,std::cout 是一个共享资源。您能否仅使用 per-thread 验证线程的启动方式?

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


【解决方案1】:

您的代码是安全的

请记住,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. 确保您不会忘记调用它。

您的代码不是并行的

您相互排除了每个线程中的所有循环体。两个线程实际上可以并行执行任何操作。使用线程的要点是,每个线程都可以处理单独的对象集(并且只能读取公共对象),因此它们不必被锁定。

在示例代码中,您确实应该锁定公共对象上的工作; 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 是足够且适当的。

【讨论】:

    【解决方案2】:

    你的代码永远不会终止,除非我不能出错。

    正如其他人指出的那样,由于互斥锁锁定到睡眠线程时会发生长时间睡眠,因此它几乎没有提供并行性的机会。

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

    是不是你还不明白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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多