c++multithreadingconcurrencystdthread

Not able to stop jthread using stop_token


very new to posting here, so apologies if it's in the wrong place.

I'm a hobby coder, so my experience is limited and while I learned C++ some years ago, I have to re-learn it periodically. Now is such a time.

I'm trying to understand jthread and it's associated stop_token mechanism.

I've finally managed to get the threads working (using VS 2022 with language standard set to C++ 20). Now though I can't get them to stop. I can't work out what I'm doing wrong here as the main thread runs to the end, but the stop_request calls seem to do nothing:

#include <iostream>
#include <chrono>
#include <thread>
#include <functional>

class Testing {
    int updates{ 0 };

public:
    void update(std::stop_token s, int i, char l) {
        while (!s.stop_requested()) {
            updates++;
            std::cout << updates << " " << l << "\n";
            std::this_thread::sleep_for(std::chrono::milliseconds(i));
        }
        std::cout << l << " stopped\n";
    }
};


int main() {

    Testing test_1, test_2;
    std::stop_token s_1, s_2;

    std::jthread t_1(&Testing::update, &test_1, s_1, 2300, 'A');
    std::jthread t_2(&Testing::update, &test_2, s_2, 700, 'B');

    std::this_thread::sleep_for(std::chrono::milliseconds(10000));
    std::cout << "Main thread done\n";

    t_1.request_stop();
    if (t_1.joinable()) { t_1.join(); }

    t_2.request_stop();
    if (t_2.joinable()) { t_2.join(); }

    return 0;
}

If anyone can help me here I'd be most grateful!


Solution

  • There are 2 issues in your code:

    1. std::stop_token should not be instantiated separately and passed to the std::jthread. Rather it already exists internally in the std::jthread.
    2. Usually the std::stop_token associated with the std::jthread is passed automatically as a first argument to the thread function. But it does not work when you use a class method, that already requires an implied this argument.

    A Solution:

    You can use a lambda (that captures the Testing instance), as shown below.

    Note that the first parameter of the lambda is the std::stop_token which will be automatically passed from the std::jthread. It is then passed "manually" to your Testing::update method.

    #include <iostream>
    #include <chrono>
    #include <thread>
    
    class Testing {
        int updates{ 0 };
    
    public:
        void update(std::stop_token s, int i, char l) {
            while (!s.stop_requested()) {
                updates++;
                std::cout << updates << " " << l << "\n";
                std::this_thread::sleep_for(std::chrono::milliseconds(i));
            }
            std::cout << l << " stopped\n";
        }
    };
    
    int main() {
        Testing test_1, test_2;
    
        std::jthread t_1([&test_1](std::stop_token s) { test_1.update(s, 2300, 'A'); });
        std::jthread t_2([&test_2](std::stop_token s) { test_2.update(s,  700, 'B'); });
    
        std::this_thread::sleep_for(std::chrono::milliseconds(5000));
        std::cout << "Main thread done\n";
    
        t_1.request_stop();
        if (t_1.joinable()) { t_1.join(); }
    
        t_2.request_stop();
        if (t_2.joinable()) { t_2.join(); }
    }
    

    Live demo 1


    A side-note:

    A std::jthread does not have to be joined manually (although it is not wrong).
    It is automatically requested to stop and then joined in the std::jthread destructor:

    If *this has an associated thread (joinable() == true), calls request_stop() and then join().

    Therefore the last 4 lines in the code above are not really needed.
    Live demo 2