jthreadを簡易実装する

C++20で導入されたstd::jthreadは、スレッドを中断させる仕組みをもった便利なクラスです。今回は、C++14でそれを簡易的に実装してみました (C++14の機能としてはラムダ式の初期化キャプチャを使っています。ラムダ式に外の変数をムーブするために必要)。

std::thread + std::promisestd::futureを使えばできます。

std::promiseに値を設定することで中断リクエストし、スレッド側のstd::futurestd::promiseに値が設定されるまで (中断リクエストされるまで) 処理を続けます。

std::promiseには1回しか値を設定できないので (2回設定すると例外が投げられる)、2回以上設定されないようにする必要があります。

#include <thread>
#include <future>
#include <utility>

class join_thread {
    bool _stop_requested = false;
    std::promise<void> _stop_request;
    std::thread _thread;
public:
    template <class F>
    explicit join_thread(F f)
    {
        _thread = std::thread {
            [g = std::move(f), fut = _stop_request.get_future()] mutable {
                g(std::move(fut));
            }
        };
    }

    ~join_thread()
    {
        join();
    }

    void request_stop()
    {
        if (!_stop_requested) {
            _stop_requested = true;
            _stop_request.set_value();
        }
    }

    void join()
    {
        request_stop();
        if (_thread.joinable()) {
            _thread.join();
        }
    }
};

#include <iostream>
#include <chrono>
void f(std::future<void> stop_request)
{
    int sum = 0;
    // 0秒waitでfutureに値が設定されたかを確認
    while (stop_request.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
        ++sum;
    }
    std::cout << sum << std::endl;
}

int main()
{
    join_thread t{f};

    std::this_thread::sleep_for(std::chrono::milliseconds(3));

    t.request_stop();
    t.join();
}

出力例:

32244

あとがき:

std::promisestd::futureの代わりに、std::atomic<bool>をスレッド間で共有するとかでもよかったですね…そちらの方がコストが安そう。実際はどちらでもよいですが、std::threadを使ったスレッドに、いろいろな方法で外から中断リクエストを送れる、ということでした。

std::atomic<bool>を使ったバージョンも載せておきます。

#include <thread>
#include <atomic>
#include <utility>

class join_thread {
    std::atomic<bool> _stop_request{false};
    std::thread _thread;
public:
    template <class F>
    explicit join_thread(F f)
    {
        _thread = std::thread{
            [this, g = std::move(f)] mutable {
                g(_stop_request);
            }
        };
    }

    ~join_thread()
    {
        join();
    }

    void request_stop()
    {
        _stop_request.store(true);
    }

    void join()
    {
        request_stop();
        if (_thread.joinable()) {
            _thread.join();
        }
    }
};

#include <iostream>
#include <chrono>
void f(std::atomic<bool>& stop_request)
{
    int sum = 0;
    while (!stop_request.load()) {
        ++sum;
    }
    std::cout << sum << std::endl;
}

int main()
{
    join_thread t{f};

    std::this_thread::sleep_for(std::chrono::milliseconds(3));

    t.request_stop();
    t.join();
}