std::thread, std::jthread & Thread Management
Învățați să creați și să gestionați fire cu std::thread și std::jthread de la C++20. Înțelegeți alăturarea vs detașarea, anularea cooperativă cu jetoane de oprire, stocarea locală a firelor și gestionarea excepțiilor peste granițele firelor.
De ce concurență?
Procesoarele moderne au mai multe nuclee, iar pentru a le exploata programul trebuie să ruleze work concomitent. C++11 a introdus o bibliotecă de threading portabilă în, iar C++20 a îmbunătățit-o custd::jthread. Un thread este o cale independentă de execuție care împarte același spațiu de adresă ca și firul care l-a creat - care este atât puterea, cât și pericolul său.
Spre deosebire de procese, firele partajează memoria. Acest lucru face comunicarea rapidă, dar introduce și curse de date dacă două fire citesc și scriu aceeași variabilă fără sincronizare. Înainte de a aborda sincronizarea (lecția următoare), să învățăm mai întâi cum să creăm, să gestionăm și să închidem corect firele.
Crearea thread-urilor cu std::thread
Creați unstd::threadtrecându-i un apelabil (funcție, lambda, functor) și orice argument. Firul începe să se execute imediat după construcție.
#include <iostream>
#include <thread>
void greet(const std::string& name, int times) {
for (int i = 0; i < times; ++i) {
std::cout << "Hello, " << name << "! (" << i + 1 << ")\n";
}
}
int main() {
// Launch a thread running greet("Alice", 3)
std::thread t1(greet, "Alice", 3);
// Lambda-based thread
std::thread t2([]() {
std::cout << "Running in a lambda thread\n";
});
// MUST join or detach before t1/t2 are destroyed
t1.join(); // block until t1 finishes
t2.join();
std::cout << "All threads finished\n";
return 0;
}join() vs detach()
Fiecarestd::threadcare reprezintă un fir de execuție trebuie să fie fie unit, fie detașat înainte ca destructorul său să fie rulat. Dacă uitați, se apeleazăstd::terminate()și programul dvs. se blochează.
join() blochează firul apelant până când firul țintă se termină. Aceasta este implicită sigură - știți că firul este terminat și rezultatele sale sunt gata.
detach() întrerupe conexiunea dintre obiectulstd::threadși firul de operare. Firul rulează în fundal (un „fir daemon”) și nu vă mai puteți alătura. Fiți atenți: dacă firul detașat accesează variabilele stivei din funcția de generare și acea funcție revine, veți obține comportament nedefinit.
Nudetach()niciodată un fir care captează sau face referire la variabile locale din domeniul de aplicare. Când funcția de creare revine, acei localnici sunt distruși și firul detașat accesează memoria suspendată. Acesta este un comportament nedefinit și se manifestă adesea ca blocări sporadice. Dacă trebuie să detașați, transmiteți date după valoare sau utilizațistd::shared_ptrpentru a prelungi durata de viață.
std::jthread — Firul RAII (C++20)
std::jthreadrezolvă două probleme custd::thread: (1) se alătură automat în destructorul său, astfel încât să nu puteți uita niciodată și (2) acceptă anularea cooperativă prinstd::stop_token.
#include <iostream>
#include <thread>
#include <chrono>
void worker(std::stop_token stoken, int id) {
while (!stoken.stop_requested()) {
std::cout << "Worker " << id << " running...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
std::cout << "Worker " << id << " stopping gracefully\n";
}
int main() {
std::jthread jt1(worker, 1); // stop_token is auto-passed as first arg
std::jthread jt2(worker, 2);
std::this_thread::sleep_for(std::chrono::seconds(1));
// Request both threads to stop
jt1.request_stop();
jt2.request_stop();
// No need to call join() — jthread's destructor does it
std::cout << "Main thread exiting\n";
return 0;
}Interogarea hardware-ului și stocare locală de fire
std::thread::hardware_concurrency()returnează numărul de fire concurente pe care hardware-ul le acceptă. Durata de stocarethread_localoferă fiecărui fir propria copie a unei variabile.
#include <iostream>
#include <thread>
#include <vector>
thread_local int tls_counter = 0; // each thread gets its own copy
void count_work(int id) {
for (int i = 0; i < 1000; ++i) {
++tls_counter; // no data race — each thread has its own
}
std::cout << "Thread " << id << ": tls_counter = " << tls_counter << "\n";
}
int main() {
unsigned int n = std::thread::hardware_concurrency();
std::cout << "Hardware concurrency: " << n << " threads\n";
std::vector<std::thread> threads;
for (unsigned int i = 0; i < n; ++i) {
threads.emplace_back(count_work, i);
}
for (auto& t : threads) {
t.join();
}
// Each thread printed 1000 — they don't share tls_counter
return 0;
}În C++20 și mai târziu, preferațistd::jthreaddecâtstd::thread. Se alătură automat în destructorul său, eliminând riscul de a apelastd::terminatedacă uitați să vă alăturați. Mecanismul săustd::stop_tokenîncorporat oferă o modalitate curată și standardizată de a solicita anularea prin cooperare - nu este nevoie să vă rulați propriul steagatomic.
Gestionarea excepțiilor pe fire
O excepție aruncată într-un fir care nu este prins în acel fir apeleazăstd::terminate(). Excepțiile nu se propagă automat la firul de conectare.
Pentru a transfera excepții între fire, capturați-le custd::current_exception()în interiorul firului de lucru și stocați-le într-unstd::exception_ptr. Firul de unire poate apoi să se reînvârtă custd::rethrow_exception(). Mecanismulstd::future(acoperit în Lecția 4) gestionează acest lucru automat.
std::threadîncepe să se execute imediat; trebuie săjoin()saudetach()înainte de a rula destructorul săustd::jthread(C++20) se înscrie automat în destructorul său și acceptă anularea prin cooperare prinstd::stop_tokenthread_localoferă fiecărui fir propria copie independentă a unei variabile - nu este necesară sincronizareastd::thread::hardware_concurrency()vă spune câte fire de execuție poate rula hardware-ul simultan- Excepțiile dintr-un fir nu se propagă către apelant - utilizați
std::exception_ptrsaustd::futurepentru a le transfera - Nu lăsați niciodată un fir detașat să facă referire la variabile locale din domeniul de aplicare
Test — Verifică-ți cunoștințele
(15 XP)1. Ce se întâmplă dacă un obiect`std::thread`care reprezintă un fir care poate fi unit este distrus fără a apela`join()`sau`detach()`?
2. Cum acceptă`std::jthread`anularea cooperativă?
3. Ce face cuvântul cheie`thread_local`?