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().
#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.
#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.
#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;
}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 resultThis 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ă.
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ă.
std::asynclansează o sarcină și returnează unstd::future— o alternativă la nivel înalt la gestionarea manuală a firelorstd::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țieistd::shared_futurepermite mai multor fire să citească același rezultatstd::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?