奇怪的多线程行为 - CompilerExplorer 链接

Weird multithreading behavior - CompilerExplorer link

提问人:Jixxy 提问时间:6/9/2022 最后编辑:Jixxy 更新时间:6/9/2022 访问量:88

问:

TL的;DR:为什么这 https://godbolt.org/z/ohK31hW34 多线程程序会出现段错误?

解释:我遇到了多线程 C++ 应用程序的奇怪行为。应用程序具有多个线程,这些线程在由变量保护的 while 循环中循环。我在多个地方使用这个构造,所以我用&方法将其提取到一个简单的类中。std::atomic<bool>ThreadLoopStart(function)Stop()

class ThreadLoop
{
public:
  ThreadLoop(const std::string& name) : mName(name) {}
  ~ThreadLoop() { Stop(); }

  template <typename F>
  void Start(F&& function)
  {
    if (mRunning)
      return;

    std::scoped_lock lock(mMutex);
    if (mThread.joinable())
      mThread.join();

    mRunning = true;
    mThread = std::thread([&]() {
      while (mRunning)
      {
        function();
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
      }
    });
  }

  void Stop()
  {
    if (not mRunning)
      return;

    mRunning = false;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

private:
  std::atomic<bool> mRunning = false;
  std::mutex mMutex;
  std::thread mThread;
  std::string mName;
};

然后,我使用这个自定义类的一个对象作为另一个“worker”类的成员,该类分配一个特定的函数来定期执行,如下所示

class Worker1
{
public:
  void StartWorking()
  {
    mThread.Start([this]() { Work(); });
  }

  void StopWorking() { mThread.Stop(); }

private:
  ThreadLoop mThread{"worker1 loop"};

  void Work()
  {
    fmt::print("Working...\n");
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
  }
};

我把所有这些“工人”都放在另一个班级中,并在随机时间点调用/对他们进行调用(也在StartWorking()StopWorking()ThreadLoop)

class Main
{
public:
  void Start()
  {
    mThread.Start([this]() { MainLoop(); });
  }

  void Stop() { mThread.Stop(); }

private:
  ThreadLoop mThread{"main loop"};
  Worker1 mWorker1;

  void MainLoop()
  {
    if (/*something*/)
      mWorker1.StartWorking();
    else
      mWorker1.StopWorking();
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
  }
};

类中的第一个(“主循环”)启动正常,并开始调用 / ,正如预期的那样。然后触发 worker 启动自己的(“worker1 循环”),该循环在函数内部不确定地失败,例如通过ThreadLoopMainStartWorking()StopWorking()StartWorking()ThreadLoopThreadLoop::Start()

    `../nptl/pthread_mutex_lock.c:81: __pthread_mutex_lock: Assertion mutex->__data.__owner == 0 failed`

此外,根据调试器的说法,整个对象似乎未初始化/销毁(例如,变量是空的,尽管我总是提供一个非空字符串) - 这可能会导致失败 - 锁定未初始化/销毁的互斥锁。我的问题是,对象如何/为什么未初始化?我想我清楚地将它构造为每个对象内部的成员?ThreadLoopstd::string mNamestd::scoped_lockThreadLoopWorker1

C++ 多线程 pthreads std compiler-explorer

评论

0赞 Jeremy Friesner 6/9/2022
在对象被销毁之前,是否有代码可以调用您的对象?如果没有,这可能会导致您看到的问题。Stop()MainMain
0赞 Jixxy 6/9/2022
@JeremyFriesner感谢您的提示。我不认为是这样 - 程序在调用“内部”Start() 后立即崩溃。此外,Stop() 是在 ThreadLoop 析构函数中调用的,所以这应该没问题,至少我认为是这样。
2赞 Jeremy Friesner 6/9/2022
如果我正在调试它,我要做的第一件事是为所涉及的每个类实现析构函数,并(暂时)将类型 debug-prints 添加到每个析构函数中。这样我就可以亲眼看到每个对象是否(以及何时)被破坏,这将有助于我理解它为什么/何时/如何进入问题/破坏状态。cout << "Destructor called for class X" << endl;
0赞 Jeremy Friesner 6/9/2022
请注意,如果在销毁之前销毁析构函数,则调用析构函数对您没有多大帮助(从发布的代码来看,情况似乎是这样 - 成员变量的销毁顺序与它们的初始化顺序相反,并且首先在包含类中声明的成员变量在包含类中稍后声明的成员变量之前初始化)StopThreadLoopWorkerThreadLoop
2赞 Jeremy Friesner 6/9/2022
对我来说闻起来像是未定义的行为:/

答:

0赞 Jixxy 6/9/2022 #1

问题是我在里面的 lambda 中捕获了 by reference,然后线程调用了 lambda,这是一个悬空的引用。解决方法是按值捕获。T&& functionThreadLoop::Start()T&& function

原始版本:

mThread = std::thread([&](){ ... })

固定版本:

mThread = std::thread([this, function](){ ... })

更好的版本:

mThread = std::jthread([this, function](){ ... })