3

Here is my code:

#include <iostream>
#include <zconf.h>
#include <thread>

class JT {
public:
    std::jthread j1;

    JT() {
        j1 = std::jthread(&JT::init, this, std::stop_token());
    }

    void init(std::stop_token st={}) {

        while (!st.stop_requested()) {
            std::cout << "Hello" << std::endl;
            sleep(1);
        }
        std::cout << "Bye" << std::endl;
    }
};

void init_2(std::stop_token st = {}) {
    while (!st.stop_requested()) {
        std::cout << "Hello 2" << std::endl;
        sleep(1);
    }
    std::cout << "Bye 2" << std::endl;
}

int main() {
    std::cout << "Start" << std::endl;
    JT *jt = new JT();
    std::jthread j2(init_2);
    sleep(5);
    std::cout << "Finish" << std::endl;
}

Here is the output:

Start
Hello
Hello 2
Hello
Hello 2
Hello
Hello 2
Hello
Hello 2
Hello
Hello 2
Finish
Bye 2
Hello

The problem is I could get Bye 2 message but not Bye message.

I know the passed stop_token variable results in this problem but I do not know how to pass it to a member function inside another member function.

7eRoM
  • 443
  • 4
  • 14
  • I believe you should've written `j1 = std::jthread(&JT::init, this);`. Also, destructor for `*jt` has never been triggered in the code. – ALX23z Jan 13 '21 at 11:05
  • @ALX23z I had tried: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues – 7eRoM Jan 13 '21 at 11:09
  • 1
    I don't think you need to setup default values for `st` parameter, nor you need to send the token here `j1 = std::jthread(&JT::init, this, std::stop_token());`. `j1 = std::jthread(&JT::init, this);` should be fine – NutCracker Jan 13 '21 at 11:14
  • Can you please provide a [mcve] (just add the #includes) and also the output you get? If you just think about it, why would you expect `Bye` when you never stop the thread or signal the token? With `j2`, the thread is stopped when the object is destroyed. – Ulrich Eckhardt Jan 13 '21 at 11:21
  • 2
    BTW, from https://en.cppreference.com/w/cpp/thread/stop_token: "A stop_token object is not generally constructed independently, but rather retrieved from a std::jthread or std::stop_source. This makes it share the same associated stop-state as the std::jthread or std::stop_source." In other words, when you're creating it (either by default value or explicitly), you're doing something wrong. – Ulrich Eckhardt Jan 13 '21 at 11:23
  • @NutCracker As mentioned in my previous comment, I get error by using `j1 = std::jthread(&JT::init, this);` – 7eRoM Jan 13 '21 at 11:31
  • @UlrichEckhardt Post has been updated. – 7eRoM Jan 13 '21 at 11:38
  • 1
    I checked c++ reference for `std::jthread`... you cannot directly invoke a member function with a stop token. You'll have to wrap the call via a lambda function. – ALX23z Jan 13 '21 at 11:53

3 Answers3

10

If I'm understanding the problem correctly (my understanding being that for std::jthread(&JT::init, this) jthread wants to call JT::init(std::stop_token st, this), which isn't going to work), you probably want to use std::bind_front to give it a Callable that works. e.g.

    JT() {
    j1 = std::jthread(std::bind_front(&JT::init, this));
}
Hasturkun
  • 35,395
  • 6
  • 71
  • 104
  • Tnx dear. I resloved the issue and posted just some seconds before your reply. But I will check your solution. – 7eRoM Jan 13 '21 at 11:54
4

According to the useful comments, I have rewritten the class code as below:

class JT {
public:
    std::jthread j1;

    JT() {
        j1 = std::jthread(&JT::init, this);
    }

    void init() {
        auto st = j1.get_stop_token();
        while (!st.stop_requested()) {
            std::cout << "Hello" << std::endl;
            sleep(1);
        }
        std::cout << "Bye" << std::endl;
    }
};

You must get the stop_token on the fly through auto st = j1.get_stop_token();.

And the revised main function:

int main() {
    std::cout << "Start" << std::endl;
    JT *jt = new JT();
//    auto jt = std::make_unique<JT>();
    std::jthread j2(init_2);
    sleep(5);
    std::cout << "Finish" << std::endl;
    delete jt;
}

You need to delete the class object directly or use RAII (like smart pointers).

7eRoM
  • 443
  • 4
  • 14
0

The std::stop_token must be received as parameter by the JT::init function, during the thread construction. You can use either std::bind

j1 = std::jthread{ std::bind(&JT::init, this, std::placeholders::_1) };

or, more simpler, std::bind_front as in @Hasturkun answer.

Note Obtaining the std::stop_token after the thread has been constructed as bellow, results in a race condition, because the line j1 = std::jthread(&JT::init, this); involves a constructor and a move operator, the second one concurring with the line auto st = j1.get_stop_token();, as demonstrated bellow:

#include <thread>
#include <iostream>

using namespace std::chrono_literals;
class JT {
public:
    std::jthread j1;

    JT() {
       j1 = std::jthread(&JT::init, this);
    }

    ~JT() {
        j1.request_stop();
        j1.join();
    }

    void init() {

        auto st = j1.get_stop_token();
        while (!st.stop_requested()) {
            std::this_thread::sleep_for(1ms);
            std::cout << "Hello" << std::endl;
        }
       std::cout << "Bye" << std::endl;
    }
};

int main() {
    std::cout << "Start" << std::endl;
    for (int i = 0; i < 1000; i++) {
        JT jt;
        std::this_thread::sleep_for(5ms);
    }
}

Which results in:

Start
Hello
Bye
Hello
Bye
Hello
Hello
Hello
Hello
Hello
Hello
....

and program never ending. I've tested on release with gcc 12.1.0 and msvc (VS 2019 16.11.5).

This can be fixed by using the constructor initializer list, which will not call the move operator:

JT() : j1(std::jthread(&JT::init, this)) {
}