LoginSignup
8

More than 1 year has passed since last update.

posted at

updated at

C++20便利機能の紹介:自動joinスレッドと停止機構 std::jthread, stop_token

C++20標準ライブラリでは「自動joinスレッド(std::jthread)と標準の停止機構(stop token)」が追加されました。これらによりマルチスレッド・プログラミングで定型的に必要とされる 別スレッドからの処理中断要求を統一的に扱える ようになります。

C++17
#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>ヘッダにて提供されます。

C++20
#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)」はサポートされません。

非同期キャンセルを安全に取り扱うのは人類には不可能 であり、ほぼ全てのプログラミング言語やマルチスレッドライブラリで非サポート、または利用非推奨となっています。

それでも、どうしても、あなたのプログラムでは非同期キャンセルが必要だと仰るなら、どうぞご自由になさって下さい。It's none of my business.

参考ページ

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8