C++20如何使用std::stop_token停止一个已分离的std::jthread线程?

3
在C++20中,引入了std::jthread作为std::thread的更安全版本;据我理解,std::jthread在线程退出时会自行清理。
此外,引入了合作式取消的概念,这样一个std::jthread可以管理一个处理底层线程状态的std::stop_source,这个std::stop_source公开了一个std::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();
  }
}

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

在某个时刻,另一个线程可以在可能分离的线程上调用 foo::stop_foo()

我所做的是否安全?

此外,当分离线程时,C++ 句柄不再与运行线程关联,而是由操作系统管理,但线程是否仍然接收来自 std::stop_source 的停止通知?

有没有更好的方法来实现我��需的功能?在 MVSC 中,这似乎没有引发任何异常或停止程序执行,并且我已经进行了大量测试以验证此点。

那么,这个解决方案是否可移植?


3
“我正在做的事情安全吗?”- 如果在同一个 foo 实例上调用多次 start_foo(),会发生什么,请思考。 “线程是否一直接收来自 std::stop_source 的停止通知”- stop_source 拥有一个共享状态。您和分离的线程都持有 stop_source 对象,这些对象共享单个状态。因此,在分离的线程仍在运行时,您仍然可以发送信号。 - Remy Lebeau
6
无关:为什么要分离它?你似乎想要确保当 foo 超出范围时它会停止,那为什么不让 jthread 做它的事情呢? - Ted Lyngmo
3
@HashAL78 这就是我的观点。你让它停止,但你不能加入来确保它已经停止了(除非你自己添加一些方法来确保)。我想我从来没有理解过关于分离线程有什么好处。在我看来,这只会使一切更加复杂,而不是更简单。 - Ted Lyngmo
1
@HashAL78 "将线程分离可以防止在foo::stop_foo超出范围时自动停止" - 你的意思是使用 start_foo()。无论如何,这个问题只是因为你将 thread 声明为 start_foo() 的局部变量。如果你将其作为类成员存储(就像你对 stop_sourcestop_token 所做的那样,它们可以移动到 stop_foo() 中作为局部变量),那么分离就不再是一个问题了。stop_foo() 仍然可以停止线程(如果它正在运行),并且 ~foo() 可以在停止线程后 join() 线程,以确保它正常结束。 - Remy Lebeau
2
@HashAL78:“我需要我的主线程继续执行而不被“calculation”阻塞。” - 即使您不分离线程,Ted所说的也不会导致发生这种情况。基本上,只有在主线程尝试“join()”“jthread”,但“jthread”没有结束(例如由于死锁),才可能发生这种情况。 - Remy Lebeau
显示剩余4条评论
1个回答

1

如果线程在foo被销毁后访问this,那么你所写的代码可能不安全。此外,这段代码还有点复杂。更简单的方法是将放入结构体中...

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,在线程本身中插入一个,以便线程在最后一个外部引用被丢弃后如果仍在运行则可以销毁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接