C++20 使用 std::stop_token 停止分离的 std::jthread

C++20 stopping a detached std::jthread using an std::stop_token

提问人:HashAL78 提问时间:6/30/2022 最后编辑:HashAL78 更新时间:6/30/2022 访问量:920

问:

在 C++ 中,20 被引入为更安全的版本;据我了解,当线程退出时,它会自行清理。std::jthreadstd::threadstd::jthread

此外,还引入了合作取消的概念,以便管理处理底层线程状态的 an,这暴露了外部人员可以使用它来理智地读取线程状态。std::jthreadstd::stop_sourcestd::stop_sourcestd::stop_token

我所拥有的是这样的。

class foo {
  std::stop_token stok;
  std::stop_source ssource;

public:
  void start_foo() {
    // ...
    auto calculation = [this](std::stop_token inner_tok) {
      // ... (*this is used here)
      while(!inner_tok.stop_requested()) {
        // stuff
      }
    }
    auto thread = std::jthread(calculation);
    ctok = thread.get_stop_token();
    ssource = thread.get_stop_source();

    thread.detach(); // ??
  }

  void stop_foo() {
    if (ssource.stop_possible()) {
      ssource.request_stop();
    }
  }

  ~foo() {
   stop_foo();
  }
}

Note 由 管理,并且没有公共构造函数。foostd::shared_ptr

在线路的某个位置,另一个线程可以调用可能分离的线程。foo::stop_foo()

我正在做的事情安全吗?

此外,分离线程时,C++ 句柄不再与正在运行的线程关联,操作系统对其进行管理,但线程是否不断接收来自 ?std::stop_source

有没有更好的方法来实现我需要的东西?在 MVSC 中,这似乎不会引发任何异常或停止程序执行,我已经做了很多测试来验证这一点。

那么,这个解决方案是便携式的吗?

C++ 多线程 std c++20

评论

3赞 Remy Lebeau 6/30/2022
"我正在做的事情安全吗?- 想一想如果在同一实例上多次调用会发生什么。“线程是否继续接收来自 std::stop_source 的停止通知” - 保持共享状态。您和分离的线程正在保存共享单个状态的对象。所以,是的,只要分离的线程正在运行,您仍然可以向它发出信号。start_foo()foostop_sourcestop_source
6赞 Ted Lyngmo 6/30/2022
无关:为什么要分离它?你似乎想小心,以便在超出范围时停止它,那么为什么不让它做它的事情呢?foojthread
3赞 Ted Lyngmo 6/30/2022
@HashAL78 这就是我的观点。你告诉它停止,但你不能加入它以确保它停止(除非你添加一些确保自己的方法)。我想我从来不明白分离线程有什么了不起的。它只会让一切变得更加复杂,而不是更少。
1赞 Remy Lebeau 6/30/2022
@HashAL78 “分离线程可以防止它在 foo::stop_foo 超出范围时自动停止” - 你的意思是。无论如何,该问题只是因为您声明为 的局部变量。如果您将其存储为类成员(就像您存储的 and 一样,然后可以将其作为局部变量移动到中),那么分离就不再是问题了。 如果线程正在运行,仍然可以停止线程,并且可以在停止线程后停止线程以确保它干净地结束start_foo()threadstart_foo()stop_sourcestop_tokenstop_foo()stop_foo()~foo()join()
2赞 Remy Lebeau 6/30/2022
@HashAL78 “我需要我的主线程继续执行,而不是在计算时被阻塞”——即使你不分离线程,Ted 所说的任何事情都不会导致这种情况发生。基本上,这只有在主线程尝试但没有结束的情况下才会发生(例如,因为死锁)。join()jthreadjthread

答:

1赞 user3188445 6/30/2022 #1

如果线程在销毁后访问,则您编写的内容可能不安全。这也有点令人费解。一个更简单的方法是将 jthread 粘贴在结构中......thisfoo

class foo {
  std::jthread thr;

public:
  void start_foo() {
    // ...
    jthr = std::jthread([this](std::stop_token inner_tok) {
      // ... (*this is used here)
      while(!inner_tok.stop_requested()) {
        // stuff
      }
    });
  }

  void stop_foo() {
    jthr.request_stop();
  }

  ~foo() {
     stop_foo();
     // jthr.detatch(); // this is a bad idea
  }
}

为了匹配代码的语义,您可以在析构函数中取消注释,但这实际上是一个坏主意,因为这样您最终可能会在线程仍在访问它时销毁它。我上面写的代码是安全的,但显然,无论哪个线程删除了对 的最后一个引用,都必须等待 退出。如果这真的无法忍受,那么也许您想将 API 更改为在线程本身中粘贴 a,以便在删除最后一个外部引用后线程仍在运行时可以销毁。jthr.detach()foofoojthreadshared_ptrfoo