Ce face thread_local
O variabilă globală obișnuită este comună tuturor firelor de execuție, deci accesul simultan trebuie protejat. O variabilă thread_local are câte o copie separată pentru fiecare fir: fiecare lucrează pe a lui, fără conflicte și fără std::mutex.
Se poate folosi pentru variabile globale, membri static ai claselor și variabile static locale. Copia unui fir este inițializată cel târziu la prima folosire în acel fir și distrusă când firul se termină.
Exemple tipice: un generator de numere aleatorii pentru fiecare fir, un contor de operații per fir, un tampon (buffer) temporar refolosit.
Exemple
Exemplul 1: Fiecare fir are propriul contor
Ambele fire cresc „același” contor, dar fiecare are copia lui, deci fiecare ajunge la exact numărul lui de pași. Firul principal (main) are și el o copie, rămasă la 0. Rezultatele sunt salvate și afișate după join, ca ordinea afișării să nu depindă de fire.
#include <functional>
#include <print>
#include <thread>
thread_local int contor = 0;
void lucreaza(int pasi, int& rezultat) {
for (int i = 0; i < pasi; ++i) {
++contor;
}
rezultat = contor;
}
int main() {
int r1 = 0, r2 = 0;
std::thread a(lucreaza, 1000, std::ref(r1));
std::thread b(lucreaza, 500, std::ref(r2));
a.join();
b.join();
std::println("Firul a: {}", r1);
std::println("Firul b: {}", r2);
std::println("Firul principal: {}", contor);
}
Rezultat
Firul a: 1000
Firul b: 500
Firul principal: 0
Exemplul 2: Comparație cu o variabilă comună
comun este o variabilă std::atomic, comună tuturor firelor, deci adună pașii ambelor fire. propriu este separat pentru fiecare fir.
#include <atomic>
#include <functional>
#include <print>
#include <thread>
std::atomic<int> comun = 0;
thread_local int propriu = 0;
void lucreaza(int& rezultat) {
for (int i = 0; i < 100; ++i) {
++comun;
++propriu;
}
rezultat = propriu;
}
int main() {
int r1 = 0, r2 = 0;
std::thread a(lucreaza, std::ref(r1));
std::thread b(lucreaza, std::ref(r2));
a.join();
b.join();
std::println("comun = {}, propriu: {} și {}", comun.load(), r1, r2);
}
Rezultat
comun = 200, propriu: 100 și 100
Greșeli frecvente
- Trimiți adresa unei variabile
thread_localaltui fir și o folosești după ce firul proprietar s-a terminat. Copia lui a fost distrusă, deci accesul este comportament nedefinit. - Te aștepți ca o valoare setată în
mainîntr-o variabilăthread_localsă fie văzută de firele noi. Fiecare fir nou pornește cu propria copie, inițializată din nou.
Comenzi înrudite
static: Are trei sensuri: variabilă locală care își păstrează valoarea între apeluri, membru comun al clasei sau nume vizibil doar în fișierul curent.constinit: Garantează că o variabilă globală sau statică este inițializată la compilare, dar fără să o facă constantă.volatile: Spune compilatorului că valoarea se poate schimba din afara programului, deci fiecare citire și scriere trebuie făcută efectiv.std::thread: Pornește o funcție pe un fir de execuție separat, care rulează în paralel cu restul programului.std::atomic: Variabilă care poate fi citită și modificată sigur de mai multe fire, fără mutex.