HashAL78
HashAL78

Reputation: 101

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

In C++20 std::jthread was introduced as a safer version of std::thread; where std::jthread, as far as I understand, cleans up after itself when the thread exits.

Also, the concept of cooperative cancellation is introduced such that an std::jthread manages an std::stop_source that handles the state of the underlying thread, this std::stop_source exposes an std::stop_token that outsiders can use to read the state of the thread sanely.

What I have is something like this.

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 foo is managed by a std::shared_ptr, and there is no public constructor.

Somewhere along the line, another thread can call foo::stop_foo() on a possibly detached thread.

Is what I am doing safe?

Also, when detaching a thread, the C++ handle is no longer associated with the running thread, and the OS manages it, but does the thread keep receiving stop notifications from the std::stop_source?

Is there a better way to achieve what I need? In MVSC, this doesn't seem to raise any exceptions or halt program execution, and I've done a lot of testing to verify this.

So, is this solution portable?

Upvotes: 4

Views: 2575

Answers (1)

user3188445
user3188445

Reputation: 4774

What you wrote is potentially unsafe if the thread accesses this after the foo has been destroyed. It's also a bit convoluted. A simpler approach would just be to stick the jthread in the structure...

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
  }
}

To match the semantics of your code, you would uncomment the jthr.detach() in the destructor, but this is actually a bad idea since then you could end up destroying foo while the thread is still accessing it. The code I wrote above is safe, but obviously whichever thread drops the last reference to the foo will have to wait for the jthread to exit. If that's really intolerable, then maybe you want to change the API to stick a shared_ptr in the thread itself, so that the thread can destroy foo if it is still running after the last external reference is dropped.

Upvotes: 3

Related Questions