Sari la conținut

std::async, std::future și std::promise

Utilizați abstracții de concurență de nivel superior: lansați sarcini asincrone cu std::async, preluați rezultatele cu std::future și setați manual valori sau excepții cu std::promise.

Concurență bazată pe sarcini

În timp cestd::threadvă oferă control la nivel scăzut, concurența bazată pe sarcini custd::asyncșistd::futureeste adesea mai ușoară și mai puțin predispusă la erori. În loc să gestionați manual firele și starea partajată, exprimați munca ca sarcini care produc rezultate. Timpul de execuție decide cum să le programeze.

Unstd::futurereprezintă o valoare de tipTcare va fi disponibilă în viitor. Puteți apelaget()pentru a bloca până când rezultatul este gata. Dacă sarcina a aruncat o excepție,get()o reintroduce - oferindu-vă propagarea automată a excepției pe fire.

std::async — Lansarea sarcinilor asincrone

std::asynclansează un apelabil și returnează unstd::futurecare conține rezultatul. Puteți specifica o politică de lansare:std::launch::asyncforțează un nou fir, în timp cestd::launch::deferredîntârzie execuția până când este apelatget().

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;
}

Politici de lansare: asincron vs amânat

std::launch::async — Funcția este executată pe un fir nou (sau unul dintr-un pool de fire, definit de implementare). Lucrările încep imediat.

std::launch::deferred — Funcția nu este executată până cândget()sauwait()este apelat pe viitor. Se rulează în firul de apelare (evaluare leneșă). Util pentru calcule de care este posibil să nu aveți nevoie.

Implicit (nicio politică specificată) — Implementarea alege. Acesta poate fiasync,deferredsau ambele (adică decide în timpul execuției). Dacă aveți nevoie de paralelism garantat, specificați întotdeaunastd::launch::async.

std::promise — Setare manuală a rezultatelor

std::promiseeste capătul de scriere al unui canal de promisiune viitoare. Creați o promisiune, extrageți viitorul acesteia și apoi fieset_value(), fieset_exception()din orice fir.

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 — consumatori multipli

Unstd::futurepoate fi editat doar cuget()o dată (se mută valoarea în afara).std::shared_futurepermite ca multiple fire să aștepte și să citească același rezultat.

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;
}
Capcană

Unstd::futurereturnat destd::asyncare o proprietate specială: destructorul său blochează până la finalizarea sarcinii (ca unjoinimplicit). Aceasta înseamnă că, dacă renunțați la viitorul returnat, apelul „async” devine efectiv sincron:

// 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::viitor.

std::packaged_task

std::packaged_taskinclude un apelabil și oferă unstd::futurepentru rezultatul său. Spre deosebire destd::async, nu rulează automat sarcina - o invocați manual (de obicei, mutându-l într-un fir sau într-o coadă de activități). Acest lucru îl face ideal pentru construirea de pool-uri personalizate:

1. Înfășurați lucrul într-unstd::packaged_task.
2. Extragețistd::futureși dați-l apelantului.
3. Mutați sarcina într-o coadă de lucru.
4. Firele de lucru extrag sarcini din coadă și le invocă.

Bună practică

Utilizațistd::asyncatunci când doriți un rezultat de la lucrul concomitent și nu aveți nevoie de control fin al firului. Propagează automat excepțiile și gestionează durata de viață a firelor. Utilizațistd::thread(saustd::jthread) atunci când aveți nevoie de lucrători de fundal persistenti, de programare personalizată sau când construiți infrastructură, cum ar fi pool-uri de fire. Pentru lucrul de incendiu și uitare, păstrați întotdeauna în viațăstd::futurereturnat pentru a evita blocarea accidentală.

Idei cheie
  • std::asynclansează o sarcină și returnează unstd::future— o alternativă la nivel înalt la gestionarea manuală a firelor
  • std::launch::asyncforțează un nou thread;std::launch::deferredîntârzie până când este apelatget()
  • std::promiseeste sfârșitul de scriere a unui canal viitor - utilizați-l pentru setarea manuală a valorii/excepției
  • std::shared_futurepermite mai multor fire să citească același rezultat
  • std::futuredinstd::asyncse blochează în destructorul său - îl stochează întotdeauna într-o variabilă
  • std::packaged_taskinclude un apelabil cu un viitor, ideal pentru grupuri de fire personalizate

Test — Verifică-ți cunoștințele

(15 XP)

1. Ce se întâmplă dacă renunțați la`std::future`returnat de`std::async(std::launch::async, fn)`?

2. Care este diferența dintre`std::future`și`std::shared_future`?

3. Cum propagă`std::async`excepțiile de la sarcină către apelant?