簡體   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