En förbättrad tråd med C++20

En förbättrad tråd med C++20

std::jthread står för sammanfogning av tråd. Förutom std::thread (C++11), std::jthread ansluter sig automatiskt till sin förstörare och kan tillsammans avbrytas. Läs i det här inlägget för att veta varför std::jthread bör vara ditt förstahandsval.

Följande tabell ger dig en kortfattad översikt över funktionaliteten hos std::jthread .

För ytterligare information, se cppreference.com. När du vill läsa mer inlägg om std::thread , här är de:mitt inlägg om std::tråd.

För det första, varför behöver vi en förbättrad tråd i C++20? Här är den första anledningen.

Ansluter automatiskt

Detta är det icke-intuitiva beteende hos std::thread . Om en std::thread är fortfarande sammanfogningsbar, std::terminate anropas i sin destruktor. En tråd thr går att ansluta om varken thr.join() inte heller thr.detach() kallades. Låt mig visa vad det betyder.

// 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';
 
}

När det körs avslutas programmet när det lokala objektet thr går utanför räckvidden.

Båda körningarna av std::thread avsluta. I den andra körningen, tråden thr har tillräckligt med tid för att visa sitt meddelande:Joinable std::thread .

I nästa exempel använder jag std::jthread från C++20-standarden.

// 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';
 
}

Nu, tråden thr ansluter sig automatiskt till sin destruktor om den fortfarande är anslutbar som i det här fallet.

Men det här är inte allt std::jthread ger dessutom till std::thread . En std::jthread kan avbrytas i samarbete. Jag presenterade redan de allmänna idéerna om samarbetsavbrott i mitt förra inlägg:Cooperative avbrott av en tråd i C++20.

Kooperativ avbrott av en std::jthread

För att få en allmän uppfattning, låt mig presentera ett enkelt exempel.

// 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';
 
}

I huvudprogrammet startar jag de två trådarna nonInterruptable och avbrottsbar (rad 1) och 2). Till skillnad från i tråden nonInterruptable , tråden interruptable får en std::stop_token och använder den i rad (3) för att kontrollera om den avbröts:stoken.stop_requested() . Vid en stoppbegäran återvänder lambdafunktionen, och därför slutar tråden. Samtalet interruptable.request_stop() (rad 4) utlöser stoppbegäran. Detta gäller inte för föregående samtal nonInterruptable.request_stop() . Samtalet har ingen effekt.

För att göra mitt inlägg komplett, med C++20, kan du också samarbeta avbryta en villkorsvariabel.

Nya vänteöverbelastningar för std::condition_variable_any

Innan jag skriver om std::condition_variable_any , här är mitt inlägg om tillståndsvariabler.

De tre väntevarianterna wait, wait_for och wait_until av std::condition_variable_any får nya överbelastningar. Dessa överbelastningar tar en 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);

Dessa nya överbelastningar behöver ett predikat. De presenterade versionerna säkerställer att du får ett meddelande om en stoppbegäran för den godkända std::stop_token stoken signaleras. De returnerar en boolean som indikerar om predikatet evalueras till true . Denna returnerade boolean är oberoende av om ett stopp begärdes eller av om timeout utlöstes.

Efter väntande samtal kan du kontrollera om en stoppbegäran inträffade.

cv.wait(lock, stoken, predicate);
if (stoken.stop_requested()){
 // interrupt occurred
}

Följande exempel visar användningen av en villkorsvariabel med en stoppbegäran.

// 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';
 
}

Mottagartråden (rad 1) väntar på meddelande från avsändartråden (rad 2). Innan avsändartråden skickar sitt meddelande (rad 3) utlöste huvudtråden en stoppbegäran på
rad (4). Utdata från programmet visar att stoppbegäran skedde före aviseringen.

Vad händer härnäst?

Vad händer när du skriver utan synkronisering till std::cout ? Du får en röra. Tack vare C++20 har vi synkroniserade utströmmar.