std::jthread
スレッドの結合を表します。 std::thread
に加えて (C++11)、 std::jthread
自動的にそのデストラクタに参加し、協力して中断することができます。 std::jthread
の理由については、この投稿をお読みください。
次の表は、std::jthread
の機能の概要を簡潔に示しています。 .
詳細については、cppreference.com を参照してください。 std::thread
に関する投稿をもっと読みたい場合 、ここにそれらがあります:std::thread に関する私の投稿。
まず、なぜ C++20 で改善されたスレッドが必要なのですか?これが最初の理由です。
自動参加
これは非直感的です std::thread
の動作 . std::thread
の場合 まだ結合可能で、std::terminate がそのデストラクタで呼び出されます。スレッド thr
thr.join()
どちらでもない場合は参加可能です thr.detach()
でもありません と呼ばれていました。それが何を意味するかお見せしましょう。
// threadJoinable.cpp #include <iostream> #include <thread> int main() { std::cout << '\n'; std::cout << std::boolalpha; std::thread thr{[]{ std::cout << "Joinable std::thread" << '\n'; }}; std::cout << "thr.joinable(): " << thr.joinable() << '\n'; std::cout << '\n'; }
実行されると、プログラムはローカル オブジェクト thr
のときに終了します。
std::thread
の両方の実行 終了します。 2 回目の実行では、スレッド thr
メッセージを表示するのに十分な時間があります:Joinable std::thread
.
次の例では、std::jthread
を使用しています。 C++20 標準から。
// jthreadJoinable.cpp #include <iostream> #include <thread> int main() { std::cout << '\n'; std::cout << std::boolalpha; std::jthread thr{[]{ std::cout << "Joinable std::thread" << '\n'; }}; std::cout << "thr.joinable(): " << thr.joinable() << '\n'; std::cout << '\n'; }
さて、スレッド thr
この場合のようにまだ結合可能な場合、デストラクタに自動的に結合します。
std::jthread
はこれだけではありません std::thread
に追加で提供 . std::jthread
協力して中断することができます。前回の投稿「C++20 でのスレッドの協調的割り込み」で、協調的割り込みの一般的な考え方については既に説明しました。
std::jthread
の協調的中断
一般的なアイデアを得るために、簡単な例を示しましょう。
// interruptJthread.cpp #include <chrono> #include <iostream> #include <thread> using namespace::std::literals; int main() { std::cout << '\n'; std::jthread nonInterruptable([]{ // (1) int counter{0}; while (counter < 10){ std::this_thread::sleep_for(0.2s); std::cerr << "nonInterruptable: " << counter << '\n'; ++counter; } }); std::jthread interruptable([](std::stop_token stoken){ // (2) int counter{0}; while (counter < 10){ std::this_thread::sleep_for(0.2s); if (stoken.stop_requested()) return; // (3) std::cerr << "interruptable: " << counter << '\n'; ++counter; } }); std::this_thread::sleep_for(1s); std::cerr << '\n'; std::cerr << "Main thread interrupts both jthreads" << '\n'; nonInterruptable.request_stop(); interruptable.request_stop(); // (4) std::cout << '\n'; }
メイン プログラムでは、2 つのスレッド nonInterruptable
を開始します。 および割り込み可能 (行 1) および 2)。スレッド nonInterruptable
とは異なり 、スレッド interruptable
std::stop_token
を取得します (3) 行でそれを使用して、中断されたかどうかを確認します:stoken.stop_requested()
.停止要求の場合、ラムダ関数が返されるため、スレッドは終了します。呼び出し interruptable.request_stop()
(4 行目) 停止要求をトリガーします。これは、前の呼び出し nonInterruptable.request_stop()
には当てはまりません .この呼び出しは効果がありません。
私の投稿を完成させるために、C++20 では、条件変数を協調的に中断することもできます。
std::condition_variable_any
の新しい待機オーバーロード
std::condition_variable_any
について書く前に 、これは条件変数に関する私の投稿です。
待機の 3 つのバリエーション wait, wait_for
、および wait_until
std::condition_variable_any の新しいオーバーロードを取得します。これらのオーバーロードには std::stop_token
が必要です .
template <class Predicate> bool wait(Lock& lock, stop_token stoken, Predicate pred); template <class Rep, class Period, class Predicate> bool wait_for(Lock& lock, stop_token stoken, const chrono::duration<Rep, Period>& rel_time, Predicate pred); template <class Clock, class Duration, class Predicate> bool wait_until(Lock& lock, stop_token stoken, const chrono::time_point<Clock, Duration>& abs_time, Predicate pred);
これらの新しいオーバーロードには述語が必要です。提示されたバージョンは、渡された std::stop_token stoken
の停止要求があった場合に確実に通知されるようにします 通知されます。述語が true
と評価されるかどうかを示すブール値を返します。 .この返されたブール値は、停止が要求されたかどうか、またはタイムアウトがトリガーされたかどうかとは無関係です。
待機呼び出しの後、停止要求が発生したかどうかを確認できます。
cv.wait(lock, stoken, predicate); if (stoken.stop_requested()){ // interrupt occurred }
次の例は、停止リクエストでの条件変数の使用法を示しています。
// conditionVariableAny.cpp #include <condition_variable> #include <thread> #include <iostream> #include <chrono> #include <mutex> #include <thread> using namespace std::literals; std::mutex mutex_; std::condition_variable_any condVar; bool dataReady; void receiver(std::stop_token stopToken) { // (1) std::cout << "Waiting" << '\n'; std::unique_lock<std::mutex> lck(mutex_); bool ret = condVar.wait(lck, stopToken, []{return dataReady;}); if (ret){ std::cout << "Notification received: " << '\n'; } else{ std::cout << "Stop request received" << '\n'; } } void sender() { // (2) std::this_thread::sleep_for(5ms); { std::lock_guard<std::mutex> lck(mutex_); dataReady = true; std::cout << "Send notification" << '\n'; } condVar.notify_one(); // (3) } int main(){ std::cout << '\n'; std::jthread t1(receiver); std::jthread t2(sender); t1.request_stop(); // (4) t1.join(); t2.join(); std::cout << '\n'; }
受信側スレッド (1 行目) は、送信側スレッド (2 行目) の通知を待っています。送信側スレッドが通知を送信する前 (3 行目)、メイン スレッドは
(4 行目) で停止要求をトリガーしました。プログラムの出力は、通知の前に停止要求が発生したことを示しています。
次は?
std::cout
に同期せずに書き込むとどうなりますか ?あなたは混乱します。 C++20 のおかげで、出力ストリームが同期されました。