1. 導入:なぜスレッドの「安全な停止」が重要なのか?
C++でマルチスレッドプログラミングを行う際、最も頭を悩ませる問題の一つが「スレッドの終了処理」です。従来、スレッドを安全に止めるには、自前でbool型のフラグを用意し、それをミューテックスで保護して監視する必要がありました。しかし、もしスレッドが「待機中(sleepや条件変数待ち)」だったらどうでしょうか?フラグのチェックまで到達できず、プログラムが終了できないという問題が発生します。C++20で導入されたstd::jthreadとstd::stop_tokenを使えば、この課題を解決し、待機中のスレッドを即座に、かつ安全に停止させることが可能になります。
2. 基礎知識:std::jthreadとstop_tokenとは?
std::jthreadは、C++11からあったstd::threadの進化版です。最大の特徴は、デストラクタが呼ばれた際に自動的にスレッドの終了を待機(join)してくれる点です。そして、その中核機能がstd::stop_tokenです。これは「スレッドを止めてほしい」という外部からの要求を伝えるためのトークンで、スレッド内でこのトークンを監視することで、停止のタイミングを正確に知ることができます。
3. 実装/解決策:条件変数との強力なタッグ
従来、条件変数(std::condition_variable)で待機しているスレッドを止めるには、フラグを立てた後に明示的にnotifyを行う必要がありました。しかし、std::condition_variable_anyとstd::stop_tokenを組み合わせると、待機中に停止要請が来た瞬間に自動的にスレッドが叩き起こされます。これにより、コードが劇的にシンプルになり、終了処理の漏れを防ぐことができます。
4. サンプルプログラム
以下のコードは、バックグラウンドで処理を行いながら、いつでも安全に終了できるスレッドの例です。
include <iostream>
include <thread>
include <condition_variable>
include <mutex>
void worker(std::stop_token stoken) {
std::mutex mtx;
std::condition_variable_any cv;
std::unique_lock lock(mtx);
std::cout << "スレッド開始。停止要請を待機中..." << std::endl;
// stop_tokenを渡すことで、停止要請が来ると自動的に待機を終了する
// 第3引数のラムダ式は「何を待つか(条件)」を定義
bool result = cv.wait(lock, stoken, [] {
return false; // 常にfalseを返すことで、停止要請が来るまで待機し続ける
});
if (stoken.stop_requested()) {
std::cout << "停止要請を受信しました。安全に終了します。" << std::endl;
}
}
int main() {
// jthreadを作成。スコープを抜けると自動的に停止要請が送られ、joinされる
std::jthread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "メインスレッドから停止要請を送ります。" << std::endl;
// ここでtのデストラクタが呼ばれ、自動的にstop_tokenへ通知が行く
return 0;
}
5. 応用・注意点:現場で役立つアドバイス
注意点1:std::condition_variable_anyが必要
通常のstd::condition_variableはstd::unique_lock<std::mutex>しか受け付けませんが、std::stop_tokenと連携するには、より汎用的なstd::condition_variable_anyを使用する必要があります。
注意点2:リソースの解放
std::jthreadは自動でjoinしてくれますが、スレッド内で確保したメモリやファイルハンドルなどは、停止要請を受け取った後に適切にクローズするよう設計してください。
応用:
std::stop_tokenにはstop_callbackという機能もあり、停止要求時に特定の関数を強制的に呼び出すことも可能です。複雑なリソース管理が必要な場合は、これを使って「停止時に必ず呼び出されるクリーンアップ関数」を登録しておくと、より堅牢なプログラムになります。

コメント