C++20 で改善されたスレッド

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 のおかげで、出力ストリームが同期されました。