【ITニュース解説】C++ 20 - concurrency - stop_token in jthread - politely interrupting the thread...
2025年09月03日に「Dev.to」が公開したITニュース「C++ 20 - concurrency - stop_token in jthread - politely interrupting the thread...」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
C++20で導入された`stop_token`は、実行中のスレッドを安全に停止させる仕組みだ。重いダウンロード処理などをユーザーがUIから途中でキャンセルしたい場合、`stop_token`が停止要求を伝え、スレッドが協調的に処理を中断・終了する。非同期タスクの制御が容易になった。
ITニュース解説
PCで複数のアプリケーションを同時に動かすように、一つのプログラムの中でも複数の処理を並行して実行できるのがマルチスレッドである。これは、例えば時間がかかる計算処理をバックグラウンドで行いながら、ユーザーインターフェース(UI)の操作をスムーズに保つなど、プログラムの応答性を高める上で非常に有効な手法だ。しかし、バックグラウンドで実行中の処理が不要になったり、ユーザーが何らかの理由でその処理を中断したいと考える場面も少なくない。例えば、大きなファイルのダウンロード中に、ネットワークの状況が悪化したり、ユーザーが途中でダウンロードをキャンセルしたいと指示したりする場合がこれに該当する。このような時、実行中のスレッドを安全に、かつ適切に中断させる方法が求められる。
従来のC++におけるマルチスレッドプログラミングでは、実行中のスレッドを途中で中断させるための標準的な仕組みは限定的であり、プログラマーが自力で中断の仕組みを実装する必要があった。特に、複数のスレッドが共有データに同時にアクセスする「クリティカルセクション」と呼ばれる重要な処理領域では、スレッドを強制的に中断させるのは非常に危険である。共有データの状態が中途半端なままになり、プログラムの誤動作やデータ破損を引き起こす可能性があるからだ。
C++20では、この課題を解決するために「協調的スレッド中断」という新しいアプローチが導入された。これは、中断を要求する側がスレッドに「中断してほしい」という信号を送り、その信号を受け取ったスレッド自身が、自分の処理の都合の良いタイミングで安全に終了するという仕組みである。これにより、強制的な中断による危険性を避けつつ、柔軟なスレッド制御が可能になった。
この協調的スレッド中断を実現するために、C++20では主にstd::stop_token、std::stop_source、そして新しいスレッドクラスであるstd::jthreadが導入された。
std::stop_tokenは、非同期タスクの中断状態を表すオブジェクトだ。これは、タスクの実行を直接制御するものではなく、「中断要求があった」という事実を伝える役割を担う。実行中のスレッドは、このstop_tokenを使って定期的に中断要求が来ているかを確認し、要求があれば自ら処理を終了する。
std::stop_sourceは、この中断要求を発行する側のオブジェクトである。stop_sourceからstop_tokenが生成され、stop_sourceのrequest_stop()メソッドを呼び出すことで、関連するstop_tokenに中断要求が通知される。
std::jthreadはC++20で新しく追加されたスレッドクラスで、従来のstd::threadよりも多くの便利な機能を持っている。特に、jthreadはコンストラクタでstop_tokenを自動的に受け取ることができ、さらにjthreadオブジェクトが破棄される際に、自動的に関連スレッドに対して中断要求を出し、そのスレッドが安全に終了するのを待つ(「join」する)機能を持つ。これにより、プログラマーはスレッドの終了処理を忘れがちだった問題を避け、リソースリークを防ぎやすくなる。
また、std::stop_callbackという機能も存在する。これは、stop_tokenに関連付けられたオプションのコールバック関数で、中断要求が発行されたときに、追加で特定の処理を自動的に実行させたい場合に登録できるものだ。
提供されたコード例を通じて、これらのコンポーネントがどのように連携して動作するかを見てみよう。
まず、Workerクラス内のprintIncrementingValues関数は、バックグラウンドで実行されるスレッドの処理本体である。この関数は引数としてstd::stop_token stopTokenを受け取る。このstopTokenが、中断要求を監視するための「窓口」となる。
関数の核となるロジックは、while (!stopToken.stop_requested())というループである。この条件式は、「もしstopTokenに中断要求が来ていなければ」という意味で、中断要求がない間はループ内の処理を続けることを示している。ループの中では、startNumberをインクリメントして画面に表示し、その後500ミリ秒間スレッドを一時停止する処理が繰り返される。この!stopToken.stop_requested()のチェックが、スレッドが中断要求を受け入れるための最も重要な部分であり、スレッドが「協調的」に終了するために不可欠な仕組みだ。中断要求が来ると、この条件式が偽となり、ループを抜けて関数が終了し、それによってスレッドも安全に停止する。
次に、プログラムのエントリーポイントであるmain関数の内容を見ていこう。
Worker worker(1);で、Workerクラスのインスタンスが初期値1で作成される。
std::stop_source stopSource;によって、中断要求を発行・管理するためのstop_sourceオブジェクトが生成される。
std::stop_token stopToken = stopSource.get_token();では、このstop_sourceから関連するstop_tokenが取得される。このstop_tokenが、バックグラウンドで実行されるWorkerの関数に渡され、中断要求を監視するために使用される。
std::jthread ridit(&Worker::printIncrementingValues, &worker, stopToken);という行で、新しいjthreadが生成され、実行が開始される。ここで、WorkerクラスのprintIncrementingValuesメソッドがスレッド関数として指定され、workerオブジェクトのポインタと、先ほど取得したstopTokenが引数として渡される。jthreadは、このstop_tokenを内部的に管理し、必要に応じて中断要求を伝達する。
std::stop_callback callback(stopToken, []() { ... });の行では、中断要求が発行されたときに実行される匿名関数(ラムダ式)をstop_callbackとして登録している。この例では、単に「Callback executed」というメッセージを画面に表示するだけである。
std::this_thread::sleep_for(10s);によって、メインスレッドは10秒間、他の処理を中断して一時停止する。この間、バックグラウンドで実行されているjthreadは、printIncrementingValues関数に従って数字を画面に出力し続ける。
メインスレッドが10秒のスリープから目覚めた後、stopSource.request_stop();が呼び出される。この呼び出しによって、stop_sourceに関連付けられたstop_tokenに対し、「中断してほしい」という信号が送られる。
この信号を受け取ると、jthread内で実行中のprintIncrementingValues関数のループ条件!stopToken.stop_requested()がfalseとなり、ループが終了する。これにより関数が安全に終了し、jthreadも停止する。main関数が終了する際、ridit(jthreadオブジェクト)のデストラクタが自動的に呼び出され、スレッドが確実に終了したことを待つ(joinする)処理が行われるため、スレッドのライフサイクル管理が簡単になる。
プログラムを実行した結果を見ると、メインスレッドが10秒間スリープしている間に、バックグラウンドのスレッドが500ミリ秒ごとに数字を出力し続けるため、10秒間で合計20回ループが実行され、「1 2 3 ... 20」という数字が画面に表示される。メインスレッドがスリープから目覚めてstopSource.request_stop()を呼び出すと、スレッドは中断要求を検知してループを終了する。同時に、事前に登録しておいたstop_callbackが実行され、「Callback executed」というメッセージが表示されてプログラムが終了する。これは、プログラムが意図した通りにスレッドを安全に中断し、終了できたことを明確に示している。
C++20で導入されたjthread、stop_token、stop_sourceといった新しい機能は、これまで複雑だったマルチスレッドプログラミングにおけるスレッドの安全な中断処理を、よりシンプルかつ効果的に実現するものだ。システムエンジニアを目指す初心者にとっても、これらの機能は現代のC++で並行処理を扱う際の強力なツールとなるだろう。