C++20標準ライブラリでは「自動joinスレッド(std::jthread
)と標準の停止機構(stop token)」が追加されました。これらによりマルチスレッド・プログラミングで定型的に必要とされる 別スレッドからの処理中断要求を統一的に扱える ようになります。
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
using namespace std::chrono_literals;
// 自前の同期キャンセルフラグ実装
class cancel_flag {
std::atomic<bool> signaled_{ false };
public:
void signal() { signaled_ = true; }
bool operator!() { return !signaled_; }
};
// ワーカスレッド処理 with 自前キャンセルフラグ
void process(cancel_flag& flag, int n)
{
// キャンセル通知が行わていない限り...
while (!flag) {
// ワーカスレッド処理を継続する
std::this_thread::sleep_for(100ms);
std::cout << n++ << " " << std::flush;
}
std::cout << "done!" << std::endl;
// 本関数を抜けることでワーカスレッドも終了する
}
int main()
{
// ワーカスレッド起動(std::thread) with 自前キャンセルフラグ
cancel_flag flag;
std::thread worker{ process, std::ref(flag), 0 };
// メインスレッド処理: ここではスリープするだけ
std::this_thread::sleep_for(1s);
// 自前キャンセルフラグを介してワーカスレッド停止を要求
flag.signal();
// ワーカスレッド終了を必ず待機(join)すること!! or DIE
worker.join();
}
std::jthreadと停止機構
std::jthread
クラスは、従来C++11からある<thread>
ヘッダにて提供されます。停止機構に関するクラス群(std::stop_token
など)は<stop_token>
ヘッダにて提供されます。
#include <chrono>
#include <iostream>
#include <stop_token> // NEW
#include <thread>
using namespace std::chrono_literals; // ms, s
// ワーカスレッド処理 with 停止トークン
void process(std::stop_token st, int n)
{
// 停止要求が行わていない限り...
while (!st.stop_requested()) {
// ワーカスレッド処理を継続する
std::this_thread::sleep_for(100ms);
std::cout << n++ << " " << std::flush;
}
std::cout << "done!" << std::endl;
}
int main()
{
// ワーカスレッド起動(jthread)
std::jthread worker{ process, 0 };
// workerに紐づく停止トークンはprocess第1引数に渡される
// メインスレッド処理: ここではスリープするだけ
std::this_thread::sleep_for(1s);
// ワーカスレッドを明示停止したければ worker.request_stop() を呼び出す。
// worker変数スコープ終了より停止トークンに対して停止要求が自動発行され、
// ワーカスレッドが終了するまでメインスレッドは待機(join)する。
}
停止トークンstd::stop_token
を 条件変数std::condition_variable_any
の待機処理(wait
メンバ関数ファミリ)と組み合わせると、より複雑な条件によるスレッド待機処理においても「中断」を統一的に扱えます。つまり、ある条件変数に対して「指定した条件を満たす、もしくは中断要求が行われるまで待機」という処理を簡単に記述できます。
std::thread vs. std::jthread
従来からあるスレッドstd::thread
とは別に新しく自動joinスレッドstd::jthread
が追加されたことに関しては、ギモンを持つ方も居られることでしょう:
- 最初から
std::thread
で自動joinする仕様にしておけば... - 既存
std::thread
を機能拡張すれば良いんじゃないの? -
std::thread
に対するラッパー(wrapper)で十分でしょ
上記についてはC++標準化委員会にて長年(2007年頃から!)議論が行われてきた歴史があり、それらを踏まえた標準ライブラリ仕様となっています。情報源リンク集と引用の羅列ですが「std::threadデストラクタ動作検討の歴史」もご参考にどうぞ。
Wanna 非同期キャンセル?
C++20標準ライブラリが提供するのは、停止要求を明示的にチェックする必要のある「協調的(cooperatively)なキャンセル」です。スレッドを任意のタイミングで終了させる「非同期キャンセル(asynchronous cancellation)」はサポートされません。
非同期キャンセルを安全に取り扱うのは人類には不可能 であり、ほぼ全てのプログラミング言語やマルチスレッドライブラリで非サポート、または利用非推奨となっています。
- Pthreadライブラリは
pthread_cancel
関数 で非同期キャンセルをサポートしますが、デフォルトでは無効化(deferred動作)されています。非同期キャンセルを安全に利用するには、全POSIX関数の動作仕様を完全理解している必要があります。 - Pthreadライブラリは
pthread_kill
関数 を提供しますが、この関数を正しく使うのは著しく困難なタスクです。 - Java言語では歴史的事情から
Thread.stop
メソッドを提供しますが、決してこのメソッドを使ってはいけません。
それでも、どうしても、あなたのプログラムでは非同期キャンセルが必要だと仰るなら、どうぞご自由になさって下さい。It's none of my business.