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();
}