Přeskočit na obsah

std::thread, std::jthread & Thread Management

Naučte se vytvářet a spravovat vlákna pomocí std::thread a std::jthread C++20. Pochopte spojení vs odpojení, kooperativní zrušení s tokeny zastavení, místní úložiště vláken a zpracování výjimek napříč hranicemi vláken.

Proč Concurrency?

Moderní procesory mají více jader a aby je mohl váš program využívat, musí pracovat současně. C++11 představil přenosnou knihovnu vláken va C++20 ji vylepšil pomocístd::jthread. vlákno je nezávislá cesta provádění, která sdílí stejný adresní prostor jako vlákno, které jej vytvořilo – což je jeho síla i nebezpečí.

Na rozdíl od procesů vlákna sdílejí paměť. Díky tomu je komunikace rychlá, ale také zavádí závodní data, pokud dvě vlákna čtou a zapisují stejnou proměnnou bez synchronizace. Než se pustíme do synchronizace (další lekce), naučte se nejprve vytvářet, spravovat a čistě zavírat vlákna.

Vytváření vláken pomocí std::thread

std::threadvytvoříte tak, že mu předáte callable (funkce, lambda, funktor) a jakékoli argumenty. Vlákno se začne spouštět ihned po sestavení.

create_thread.cpp
#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 odpojit()

Každéstd::thread, které představuje běžící vlákno, musí být před spuštěním jeho destruktoru připojeno nebo odpojeno. Pokud zapomenete, zavolá sestd::terminate()a váš program se zhroutí.

join() blokuje volající vlákno, dokud cílové vlákno neskončí. Toto je bezpečné výchozí nastavení – víte, že vlákno je hotové a jeho výsledky jsou připraveny.

detach() přeruší spojení mezi objektemstd::threada vláknem OS. Vlákno běží na pozadí ("vlákno démona") a vy se do něj již nemůžete připojit. Buďte opatrní: pokud odpojené vlákno přistupuje k proměnným zásobníku z funkce spawnování a tato funkce se vrací, dostanete nedefinované chování.

Úskalí

Nikdydetach()vlákno, které zachycuje nebo odkazuje na místní proměnné z rozsahu vytváření. Když se funkce vytváření vrátí, tito místní jsou zničeni a odpojené vlákno přistupuje k visící paměti. Toto je nedefinované chování a často se projevuje jako sporadické pády. Pokud se musíte odpojit, předejte data podle hodnoty nebo použijtestd::shared_ptrk prodloužení životnosti.

std::jthread — RAII vlákno (C++20)

std::jthreadřeší dva problémy sstd::thread: (1) automaticky se připojí ke svému destruktoru, takže nikdy nezapomenete, a (2) podporuje kooperativní zrušení prostřednictvímstd::stop_token.

jthread_demo.cpp
#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;
}

Dotazování hardwaru a místního úložiště vláken

std::thread::hardware_concurrency()vrací počet souběžných vláken, která hardware podporuje. Doba ukládáníthread_localposkytuje každému vláknu jeho vlastní kopii proměnné.

hardware_tls.cpp
#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;
}
Osvědčený postup

V C++20 a novějších upřednostňujtestd::jthreadpředstd::thread. Automaticky se připojí ke svému destruktoru, čímž se eliminuje riziko volánístd::terminate, pokud se zapomenete připojit. Jeho vestavěný mechanismusstd::stop_tokenposkytuje čistý, standardizovaný způsob, jak požádat o kooperativní zrušení – není třeba vytvářet vlastní příznakatomic shouldStop.

Zpracování výjimek napříč vlákny

Výjimka vyvolaná uvnitř vlákna, která není zachycena v rámci tohoto vlákna, volástd::terminate(). Výjimky se ne automaticky rozšíří do připojovaného vlákna.

Chcete-li přenést výjimky mezi vlákny, zachyťte je pomocístd::current_exception()uvnitř pracovního vlákna a uložte je dostd::exception_ptr. Spojovací vlákno pak může být znovu vyvoláno pomocístd::rethrow_exception(). Mechanismusstd::future(popsaný v lekci 4) to zvládá automaticky.

Klíčové poznatky
  • std::threadse spustí okamžitě; musítejoin()nebodetach()před spuštěním jeho destruktoru
  • std::jthread(C++20) se automaticky připojí ke svému destruktoru a podporuje kooperativní zrušení prostřednictvímstd::stop_token
  • thread_localdává každému vláknu jeho vlastní nezávislou kopii proměnné – není potřeba žádná synchronizace
  • std::thread::hardware_concurrency()vám říká, kolik vláken může hardware běžet souběžně
  • Výjimky ve vláknu se na volajícího nepřenesou — k jejich přenosu použijtestd::exception_ptrnebostd::future
  • Nikdy nedovolte, aby odpojené vlákno odkazovalo na místní proměnné z rozsahu vytváření

Kvíz — Otestujte své znalosti

(15 XP)

1. Co se stane, když je objekt`std::thread`představující připojitelné vlákno zničen bez volání`join()`nebo`detach()`?

2. Jak`std::jthread`podporuje kooperativní zrušení?

3. Co dělá klíčové slovo`thread_local`?