C++20で導入されたstd::jthreadは、スレッドを中断させる仕組みをもった便利なクラスです。今回は、C++14でそれを簡易的に実装してみました (C++14の機能としてはラムダ式の初期化キャプチャを使っています。ラムダ式に外の変数をムーブするために必要)。
std::thread + std::promiseとstd::futureを使えばできます。
std::promiseに値を設定することで中断リクエストし、スレッド側のstd::futureはstd::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;
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::promiseとstd::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();
}