繁体   English   中英

在 std::thread 的移动分配中调用终止

[英]Terminate called in move assignment of std::thread

我有一个由多个用户使用的多线程应用程序。 对于某些用户,运行应用程序会导致

terminate called without an active exception
Aborted

使用 GDB 运行应用程序会产生以下 output:

Thread 1 ... received signal SIGABRT, Aborted.
__GI__raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
51       ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) where
#0 _GI_raise (sig=sig@entry=6) at ./sysdeps/unix/sysv/linux/raise.c:51
#1 0x00007f46925e9921 in GI_abort () at abort.c:79
#2 0x0000744692404957 in ?? (from /usr/lib/x86_64-linux-gnu/libstdc++.50.6
#3 0x00007F4692fe2ae6 in ?? (from /usr/lib/x86_64-linux-gnu/libstdc++.50.6
#4 0x00007F4692fe2b21 in std::terminate() ()
  from /usr/lib/x86_64-linux-gnu/libstdc++.50.6
#5 0X000056407cb17783 in std::thread::operator=(std::thread&&) ()
...

在线查看,该错误似乎是由于处理线程清理不当造成的(其中一个线程仍可连接)。 下面的代码是在应用程序中找到的代码示例。

看门狗

class WatchDog {
  std::thread t_;
  std::atomic<bool> run_;

public:
  WatchDog(){};

  void Init() { t_ = std::thread(&WatchDog::Log, this); }

  void Log() {

    run_ = true;
    while (run_) {
      std::cout << "Operational" << std::endl;
      std::this_thread::sleep_for(std::chrono::milliseconds(200));
      // throw;
    }
  }

  void Stop() { run_ = false; }

  ~WatchDog() {
    if (t_.joinable())
      t_.join();
  }
};

主要的

int main() {
  WatchDog dog;
  dog.Init();

  std::this_thread::sleep_for(std::chrono::seconds(1));
  dog.Stop();
}

示例剥离的应用程序运行没有失败,并且在实际应用程序中也遵循了 RAII 习惯用法。 仔细回顾 GDB 结果,似乎终止调用是在移动赋值构造函数本身中对t_本身进行的。 关于如何发生这种情况的任何解释以及调试它的建议? 感谢你的帮助。


编辑

谢谢,Slava,BitTickler,cdhowie .. 我不知道std::optional 我注意到我在其他几个地方犯了设计错误,所以想制作一个 ThreadWrapper class。 归功于 https://thispointer.com/c11-how-to-use-stdthread-as-a-member-variable-in-class/ 通过将其传递this std::thread的能力对其进行了扩展,因为我确实需要访问 WatchDog class。

class ThreadWrapper {
public:
  // Delete copy constructor
  ThreadWrapper(const ThreadWrapper &) = delete;

  // Delete assignment constructor
  ThreadWrapper &operator=(const ThreadWrapper &) = delete;

  // Parameterized Constructor
  template <class F, class... Args>
  explicit ThreadWrapper(F&& func, Args &&... args)
      : thread_(std::forward<F>(func), std::forward<Args>(args)...) {}

  // Move constructor
  ThreadWrapper(ThreadWrapper &&obj) : thread_(std::move(obj.thread_)) {}

  // Move Assignment Constructor
  ThreadWrapper &operator=(ThreadWrapper &&obj) {
    if (thread_.joinable()) {
      thread_.join();
    }
    thread_ = std::move(obj.thread_);
    return *this;
  }

  ~ThreadWrapper() {
    if (thread_.joinable()) {
      thread_.join();
    }
  }

private:
  std::thread thread_;
};

现在, WatchDog class 中的 ThreadWrapper object 是否可以避免对Init的潜在调用两次? 计划在 WatchDog 构造函数中初始化 threadwrapper_,但据我所知更多。 再次感谢大家。

threadwrapper_ = ThreadWrapper(&WatchDog::Log, this);

std::thread::operator=

如果*this仍有关联的运行线程(即joinable() == true ),则调用std::terminate()

看起来您的t_有一个关联的正在运行的线程,但您的非真实代码没有显示这一点。

为了建立现有的答案,最有可能发生的是您在同一个 object 上调用Init()两次,这会导致分配给现有(可连接)线程,这是不允许的。 考虑使用新接口重新设计此 class。

  • Init()应该隐式发生在构造上。
  • Stop()应该在销毁时隐式发生。
  • 使Log()私有且 const 正确。

使用此实现,不可能意外调用Init()两次,并且在销毁时会自动进行清理。 (在您的实现中,如果您忘记调用Stop()则线程将永远不会加入,因为run_永远不会设置为 false。)

如果您希望能够拥有“可能处于活动状态的WatchDog ”,那么您可以简单地使用std::optional<WatchDog>

class WatchDog {
  std::atomic<bool> run_;
  std::thread t_;

public:
  WatchDog();
  ~WatchDog();

private:
  void Log() const;
};

WatchDog::WatchDog() :
  run_{true},
  t_{&WatchDog::Log, this} {}

void WatchDog::Log() const {
  while (run_) {
    std::cout << "Operational" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    // throw;
  }
}

WatchDog::~WatchDog() {
  run_ = false;
  if (t_.joinable()) {
    t_.join();
  }
}

使用此实现,您给定的main()变为:

int main() {
    WatchDog dog;
    std::this_thread::sleep_for(std::chrono::seconds(2));
}

如果您想在更动态的设置中更明确地控制 object 的生命周期,这就是std::optional的用武之地:

int main() {
    std::optional<WatchDog> dog;
    dog.emplace(); // Replaces Init()
    std::this_thread::sleep_for(std::chrono::seconds(2));
    dog.reset(); // Replaces Stop()
}

显然,这将具有与其他main()示例相同的可观察行为,但重点是说明如果 object 的生命周期需要更复杂且不受值的生命周期的精确限制,您如何创建和销毁 object。

这解决了Init()被调用两次的问题,因为std::optional::emplace()将在创建新值之前破坏包含的值。 当然,如果您只想确保有一个活动的WatchDog (而不是不必要地破坏和创建一个),那么您可以执行类似if (.dog) { dog;emplace(); } if (.dog) { dog;emplace(); }

作为旁注,如果WatchDog::Log从不使用this ,则可以将其设置为static ,然后特定线程不会绑定到特定的WatchDog实例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM