std::thread în C++

Pornește o funcție pe un fir de execuție separat, care rulează în paralel cu restul programului.

Sintaxă
#include <thread>
std::thread t(functie, argumente...);
void join();
bool joinable() const noexcept;

Ce face std::thread

std::thread t(f, a, b); pornește imediat f(a, b) pe un fir nou. Firul principal (main) continuă în paralel. Argumentele sunt copiate; pentru referințe folosește std::ref.

t.join() așteaptă ca firul să termine. Înainte ca obiectul std::thread să fie distrus, trebuie apelat join() sau detach(); altfel programul se oprește cu std::terminate. joinable() spune dacă mai trebuie apelat unul dintre ele.

Firele care afișează în același timp își amestecă mesajele într-o ordine imprevizibilă. În exemplele de mai jos fiecare fir își scrie rezultatul într-un loc separat, iar afișarea se face după join(), deci rezultatul este mereu același.

Parametri

Parametru Descriere
functie Funcția, lambda sau obiectul apelabil rulat pe firul nou.
argumente Argumentele, copiate în firul nou.

Ce returnează

Un obiect care reprezintă firul de execuție. join() nu returnează nimic; rezultatele se trimit prin variabile, std::future sau std::promise.

Exemple

Exemplul 1: Două calcule în paralel

#include <numeric>
#include <print>
#include <thread>
#include <vector>

int main() {
    std::vector<int> v(1000);
    std::iota(v.begin(), v.end(), 1);   // 1, 2, ..., 1000

    long long suma1 = 0, suma2 = 0;
    std::thread t1([&] { suma1 = std::accumulate(v.begin(), v.begin() + 500, 0LL); });
    std::thread t2([&] { suma2 = std::accumulate(v.begin() + 500, v.end(), 0LL); });

    t1.join();
    t2.join();
    std::println("{} + {} = {}", suma1, suma2, suma1 + suma2);
}

Rezultat

125250 + 375250 = 500500

Exemplul 2: Mai multe fire, fiecare cu rezultatul lui

Fiecare fir scrie doar în elementul lui din vector, deci nu e nevoie de sincronizare. Afișarea se face după ce toate au terminat.

#include <print>
#include <string>
#include <thread>
#include <vector>

int lungime(const std::string& oras) { return static_cast<int>(oras.size()); }

int main() {
    std::vector<std::string> orase{"Iași", "Cluj-Napoca", "Constanța"};
    std::vector<int> rezultate(orase.size());
    std::vector<std::thread> fire;

    for (std::size_t i = 0; i < orase.size(); ++i) {
        fire.emplace_back([&, i] { rezultate[i] = lungime(orase[i]); });
    }
    for (auto& t : fire) t.join();

    for (std::size_t i = 0; i < orase.size(); ++i) {
        std::println("{}: {} octeți", orase[i], rezultate[i]);
    }
}

Rezultat

Iași: 5 octeți
Cluj-Napoca: 11 octeți
Constanța: 10 octeți

Exemplul 3: Ordinea mesajelor nu este garantată

Dacă firele afișează direct, ordinea se schimbă de la o rulare la alta. De aceea exemplul nu este rulat la generarea paginii.

#include <print>
#include <thread>

int main() {
    std::thread a([] { std::println("Firul A"); });
    std::thread b([] { std::println("Firul B"); });
    std::println("Firul principal");
    a.join();
    b.join();
}

Rezultat posibil (diferă la fiecare rulare)

Firul principal
Firul B
Firul A

Greșeli frecvente

  • Uiți join(). La distrugerea obiectului std::thread programul se oprește brusc cu std::terminate. Folosește std::jthread, care face join automat.
  • Două fire modifică aceeași variabilă fără std::mutex sau std::atomic. Este o „cursă de date” (data race): comportament nedefinit, cu rezultate greșite care apar doar uneori.
  • Capturezi prin referință ([&]) variabila de buclă i. Până pornește firul, i se poate schimba; captureaz-o prin valoare ([&, i]).

Comenzi înrudite

  • std::jthread: Fir de execuție care face join automat la distrugere și poate fi rugat să se oprească (stop_token).
  • std::thread::detach: Desparte firul de obiectul std::thread: rulează mai departe singur și nu mai poate fi așteptat cu join.
  • std::mutex: Încuietoare pentru date comune: un singur fir de execuție o poate ține la un moment dat.
  • std::async: Rulează o funcție (de obicei pe alt fir) și îți dă un std::future din care iei rezultatul mai târziu.
  • std::ref: Împachetează o referință ca să poată fi transmisă prin funcții care copiază argumentele (thread, bind).