はじめに
マルチスレッドプログラミングにおける難しい課題の一つが、「スレッドを安全に、かつ綺麗に停止させる」ことです。旧来のstd::thread
では、スレッドを外部から中断させるための標準的な仕組みがなく、複雑な実装が必要でした。
この問題を解決するために、C++20という新しい規格で中断の仕組みを内蔵した新しいスレッドクラス std::jthread
が導入されました。
この記事では、jthread
を使って、中断可能なワーカースレッドを作成し、メインスレッドから安全に停止させる方法を解説します。
【前提】C++20とは?
C++というプログラミング言語は、数年ごとに改訂され、新しい機能が追加されたり、より安全で便利になったりしています。その改訂版には、発行された年にちなんで名前が付けられます。
C++20(シーピープラスにじゅう、またはにーぜろ)は、2020年に正式化された、C++言語のメジャーアップデート版のことです。
この記事で紹介する std::jthread
や std::stop_token
は、このC++20で新たに追加された機能です。そのため、これらのコードをコンパイルするには、C++20に対応したコンパイラ(と、その機能を有効にするための設定)が必要になります。
jthread
を使った中断可能なスレッドのサンプルコード
このコードは、worker_task
というバックグラウンドタスクを jthread
で起動します。main
関数は3秒待った後、スレッドに対して停止を要求します。ワーカースレッドは、ループの各回で停止要求が来ていないかを確認し、要求を検知したらループを抜けて正常に終了します。
完成コード
#include <iostream>
#include <thread> // jthread, stop_token を使うために必要
#include <chrono>
using namespace std;
// 1. スレッドで実行されるタスク
// 最初の引数で std::stop_token を受け取る
void worker_task(stop_token st, const string& task_name) {
int counter = 0;
// 2. stop_requested()で、外部から停止要求が来ていないかチェック
while (!st.stop_requested()) {
cout << task_name << ": 実行中... (" << ++counter << "秒経過)" << endl;
// 1秒待機
this_thread::sleep_for(chrono::seconds(1));
}
// ループを抜けたら、停止要求を検知したことを示す
cout << task_name << ": 停止要求を検知。タスクを終了します。" << endl;
}
int main() {
cout << "メインスレッド: ワーカースレッドをjthreadで起動します。" << endl;
// 3. jthreadでスレッドを起動
jthread worker(worker_task, "タスクA");
cout << "メインスレッド: 3秒後に停止要求を送ります。" << endl;
this_thread::sleep_for(chrono::seconds(3));
// 4. スレッドに停止を要求する
worker.request_stop();
// 5. jthreadはデストラクタで自動的にjoin()されるので、明示的なjoinは不要
cout << "メインスレッド: 終了。" << endl;
return 0;
}
コードの解説
1. void worker_task(stop_token st, ...)
jthread
で起動する関数の最初の引数を std::stop_token
型にすると、そのスレッドに関連付けられた「停止トークン」を自動的に受け取ることができます。この stop_token
オブジェクトを通じて、外部からの停止要求を検知します。
2. while (!st.stop_requested())
これが、スレッドが自律的に停止するための核心部分です。
st.stop_requested()
: 停止要求があればtrue
を、なければfalse
を返します。- この
while
ループは、停止要求が来るまで処理を続けます。
ワーカースレッドは、このように定期的に stop_requested()
をチェックし、true
になったら後片付けの処理などを行って、自ら処理を完了させるのが、安全な中断処理の基本です。
3. jthread worker(...)
std::jthread
は std::thread
とほぼ同じように使えます。コンストラクタに、実行したい関数と、その関数に渡す引数を指定します。
4. worker.request_stop()
main
スレッドから、worker
スレッドに対して「停止してください」という要求を送るためのメソッドです。これを呼び出すと、worker
スレッドに紐付いた stop_token
の状態が「停止要求あり」に変わります。
5. jthread
の自動 join
通常の std::thread
は、破棄される前に必ず .join()
か .detach()
を呼ぶ必要がありましたが、jthread
はデストラクタが呼ばれる際に、自動的に .join()
を呼び出してくれます。これにより、join
の呼び忘れによるプログラムのクラッシュを防ぐことができ、コードがより安全かつシンプルになります。
まとめ
今回は、C++20の jthread
と stop_token
を使って、安全に中断可能なスレッドを作成する方法を解説しました。
std::jthread
: 中断機構と自動join
機能を備えた、安全なスレッドクラス。- 関数の最初の引数を
std::stop_token
にすると、停止トークンを受け取れる。 - ループの中で
stop_token.stop_requested()
を定期的にチェックし、スレッドが自律的に終了するように実装する。 - 外部からは
jthread.request_stop()
で、スレッドに停止要求を送る。
jthread
は、従来の thread
が抱えていた問題を解決する、現代C++におけるスレッドプログラミングの新しい標準です。