Přeskočit na obsah

std::async, std::future & std::promise

Používejte abstrakce souběžnosti vyšší úrovně: spouštějte asynchronní úlohy pomocí std::async, načtěte výsledky pomocí std::future a ručně nastavte hodnoty nebo výjimky pomocí std::promise.

Souběžnost založená na úkolech

Zatímcostd::threadvám poskytuje ovládání na nízké úrovni, souběžnost založená na úkolech sstd::asyncastd::futureje často jednodušší a méně náchylná k chybám. Namísto ruční správy vláken a sdíleného stavu vyjadřujete práci jako úkoly, které přinášejí výsledky. Runtime rozhodne, jak je naplánovat.

std::futurepředstavuje hodnotu typuT, která bude dostupná v budoucnu. Chcete-li blokovat, můžete volatget(), dokud nebude výsledek připraven. Pokud úloha vyvolala výjimku,get()ji znovu vyvolá, čímž získáte automatické šíření výjimky napříč vlákny.

std::async — Spouštění asynchronních úloh

std::asyncspustí callable a vrátístd::futureobsahující výsledek. Můžete zadat spouštěcí politiku:std::launch::asyncvynutí nové vlákno, zatímcostd::launch::deferredzpozdí provedení, dokud není zavolánoget().

async_parallel_sum.cpp
#include <iostream>
#include <future>
#include <chrono>
#include <numeric>
#include <vector>

long long parallel_sum(const std::vector<int>& v, size_t start, size_t end) {
    if (end - start < 1000) {
        return std::accumulate(v.begin() + start, v.begin() + end, 0LL);
    }
    size_t mid = start + (end - start) / 2;

    // Launch left half asynchronously
    auto left = std::async(std::launch::async,
                           parallel_sum, std::cref(v), start, mid);

    // Compute right half in this thread
    long long right_sum = parallel_sum(v, mid, end);

    return left.get() + right_sum;  // .get() blocks until left is done
}

int main() {
    std::vector<int> data(1'000'000, 1);

    auto start = std::chrono::high_resolution_clock::now();
    long long result = parallel_sum(data, 0, data.size());
    auto end = std::chrono::high_resolution_clock::now();

    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "Sum: " << result << " in " << ms.count() << "ms\n";
    return 0;
}

Zásady spouštění: asynchronní vs odložené

std::launch::async — Funkce je spuštěna na novém vláknu (nebo na vlákně z fondu vláken, definované implementací). Práce začíná okamžitě.

std::launch::deferred — Funkce se neprovede, dokud nebude v budoucnu vyvolánoget()nebowait(). Běží ve volajícím vláknu (líné hodnocení). Užitečné pro výpočty, které možná nepotřebujete.

Výchozí (není zadána žádná politika) — Implementace si vybere. Může to býtasync,deferrednebo obojí (to znamená, že rozhoduje za běhu). Pokud potřebujete zaručený paralelismus, vždy zadejtestd::launch::async.

std::promise — Ruční nastavení výsledku

std::promiseje write konec kanálu slibujícího budoucnost. Vytvoříte příslib, extrahujete jeho budoucnost a poté buďset_value()neboset_exception()z libovolného vlákna.

promise_demo.cpp
#include <iostream>
#include <thread>
#include <future>
#include <stdexcept>

void compute(std::promise<int> prom, int x) {
    try {
        if (x < 0) {
            throw std::invalid_argument("negative input");
        }
        int result = x * x;
        prom.set_value(result);  // fulfil the promise
    } catch (...) {
        prom.set_exception(std::current_exception());  // propagate error
    }
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();  // get the read end

    std::thread t(compute, std::move(prom), 7);

    try {
        int result = fut.get();  // blocks until value or exception
        std::cout << "Result: " << result << "\n";  // prints 49
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << "\n";
    }

    t.join();
    return 0;
}

std::shared_future — Více spotřebitelů

std::futuremůže býtget()-ed pouze jednou (přesune hodnotu ven).std::shared_futureumožňuje více vláknům čekat a číst stejný výsledek.

shared_future.cpp
#include <iostream>
#include <future>
#include <thread>
#include <vector>

int main() {
    auto shared = std::async(std::launch::async, []() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return 42;
    }).share();  // convert future to shared_future

    // Multiple threads can all read the same result
    std::vector<std::thread> consumers;
    for (int i = 0; i < 5; ++i) {
        consumers.emplace_back([shared, i]() {
            int val = shared.get();  // all 5 threads get 42
            std::cout << "Consumer " << i << " got: " << val << "\n";
        });
    }

    for (auto& t : consumers) t.join();
    return 0;
}
Úskalí

std::futurevrácenýstd::asyncmá speciální vlastnost: jeho destruktor blokuje, dokud není úkol dokončen (jako implicitníjoin). To znamená, že pokud zahodíte vrácenou budoucnost, "asynchronní" volání se stane efektivně synchronním:

// SURPRISE — this blocks! The temporary future is destroyed
// at the semicolon, which waits for the task to finish.
std::async(std::launch::async, long_computation);

// CORRECT — store the future to let work proceed in parallel
auto fut = std::async(std::launch::async, long_computation);
// ... do other work ...
fut.get();  // now wait for the result

This is the most common pitfall with std::async. Always capture the returned std::future.

std::packaged_task

std::packaged_taskobaluje callable a poskytujestd::futurepro jeho výsledek. Na rozdíl odstd::asyncne automaticky spustí úlohu – vyvoláte ji ručně (obvykle přesunutím do vlákna nebo do fronty úloh). Díky tomu je ideální pro vytváření vlastních skupin vláken:

1. Zabalte práci dostd::packaged_task.
2. Rozbaltestd::futurea předejte jej volajícímu.
3. Přesuňte úlohu do pracovní fronty.
4. Pracovní vlákna vytahují úlohy z fronty a vyvolávají je.

Osvědčený postup

Použijtestd::async, když chcete výsledek ze souběžné práce a nepotřebujete jemnou kontrolu vláken. Automaticky šíří výjimky a spravuje životnost vláken. Použijtestd::thread(nebostd::jthread), když potřebujete trvalé pracovníky na pozadí, vlastní plánování nebo když budujete infrastrukturu, jako jsou fondy vláken. Pro práci s ohněm a zapomnění vždy udržujte vrácenýstd::futurenaživu, abyste předešli náhodnému zablokování.

Klíčové poznatky
  • std::asyncspustí úlohu a vrátístd::future– alternativa na vysoké úrovni k ruční správě vláken
  • std::launch::asyncvynutí nové vlákno;std::launch::deferredzpozdí, dokud není volánoget()
  • std::promiseje konec pro zápis budoucího kanálu – použijte jej pro ruční nastavení hodnoty/výjimky
  • std::shared_futureumožňuje více vláknům číst stejný výsledek
  • std::futurezstd::asyncblokuje ve svém destruktoru — vždy jej ukládejte do proměnné
  • std::packaged_taskspojuje callable s budoucností, ideální pro vlastní fondy vláken

Kvíz — Otestujte své znalosti

(15 XP)

1. Co se stane, když zahodíte`std::future`vrácený`std::async(std::launch::async, fn)`?

2. What is the difference between`std::future`and`std::shared_future`?

3. Jak`std::async`šíří výjimky z úlohy na volajícího?