C++
Futures en beloften
Zoeken…
Invoering
Beloften en toekomsten worden gebruikt om een enkel object van de ene draad naar de andere te verplaatsen.
Een std::promise
object wordt ingesteld door de thread die het resultaat genereert.
Een std::future
object kan worden gebruikt om een waarde op te halen, om te testen of een waarde beschikbaar is of om de uitvoering te stoppen totdat de waarde beschikbaar is.
std :: future en std :: belofte
In het volgende voorbeeld wordt een belofte ingesteld voor gebruik door een andere thread:
{
auto promise = std::promise<std::string>();
auto producer = std::thread([&]
{
promise.set_value("Hello World");
});
auto future = promise.get_future();
auto consumer = std::thread([&]
{
std::cout << future.get();
});
producer.join();
consumer.join();
}
Voorbeeld van uitgestelde async
Deze code implementeert een versie van std::async
, maar het gedraagt zich alsof async
altijd wordt aangeroepen met het deferred
startbeleid. Deze functie heeft ook geen speciaal future
gedrag van async
; de teruggekeerde future
kan worden vernietigd zonder ooit zijn waarde te verwerven.
template<typename F>
auto async_deferred(F&& func) -> std::future<decltype(func())>
{
using result_type = decltype(func());
auto promise = std::promise<result_type>();
auto future = promise.get_future();
std::thread(std::bind([=](std::promise<result_type>& promise)
{
try
{
promise.set_value(func());
// Note: Will not work with std::promise<void>. Needs some meta-template programming which is out of scope for this example.
}
catch(...)
{
promise.set_exception(std::current_exception());
}
}, std::move(promise))).detach();
return future;
}
std :: packaged_task en std :: future
std::packaged_task
bundelt een functie en de bijbehorende belofte voor het retourtype:
template<typename F>
auto async_deferred(F&& func) -> std::future<decltype(func())>
{
auto task = std::packaged_task<decltype(func())()>(std::forward<F>(func));
auto future = task.get_future();
std::thread(std::move(task)).detach();
return std::move(future);
}
De draad begint onmiddellijk te lopen. We kunnen het losmaken of aan het einde van de scope meedoen. Wanneer de functieaanroep naar std :: thread is voltooid, is het resultaat gereed.
Merk op dat dit enigszins verschilt van std::async
waar de geretourneerde std::future
wanneer vernietigd daadwerkelijk zal blokkeren totdat de thread is voltooid.
std :: future_error en std :: future_errc
Als niet aan de beperkingen voor std :: belofte en std :: future wordt voldaan, wordt een uitzondering van het type std :: future_error gegenereerd.
Het lid van de foutcode is van het type std :: future_errc en waarden zijn zoals hieronder, samen met enkele testgevallen:
enum class future_errc {
broken_promise = /* the task is no longer shared */,
future_already_retrieved = /* the answer was already retrieved */,
promise_already_satisfied = /* the answer was stored already */,
no_state = /* access to a promise in non-shared state */
};
Inactieve belofte:
int test()
{
std::promise<int> pr;
return 0; // returns ok
}
Actieve belofte, ongebruikt:
int test()
{
std::promise<int> pr;
auto fut = pr.get_future(); //blocks indefinitely!
return 0;
}
Dubbel ophalen:
int test()
{
std::promise<int> pr;
auto fut1 = pr.get_future();
try{
auto fut2 = pr.get_future(); // second attempt to get future
return 0;
}
catch(const std::future_error& e)
{
cout << e.what() << endl; // Error: "The future has already been retrieved from the promise or packaged_task."
return -1;
}
return fut2.get();
}
Std instellen :: belofte waarde twee keer:
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
try{
std::promise<int> pr2(std::move(pr));
pr2.set_value(10);
pr2.set_value(10); // second attempt to set promise throws exception
}
catch(const std::future_error& e)
{
cout << e.what() << endl; // Error: "The state of the promise has already been set."
return -1;
}
return fut.get();
}
std :: future en std :: async
In het volgende naïeve parallelle sorteervoorbeeld, wordt std::async
gebruikt om meerdere parallelle merge_sort-taken te starten. std::future
wordt gebruikt om op de resultaten te wachten en deze te synchroniseren:
#include <iostream>
using namespace std;
void merge(int low,int mid,int high, vector<int>&num)
{
vector<int> copy(num.size());
int h,i,j,k;
h=low;
i=low;
j=mid+1;
while((h<=mid)&&(j<=high))
{
if(num[h]<=num[j])
{
copy[i]=num[h];
h++;
}
else
{
copy[i]=num[j];
j++;
}
i++;
}
if(h>mid)
{
for(k=j;k<=high;k++)
{
copy[i]=num[k];
i++;
}
}
else
{
for(k=h;k<=mid;k++)
{
copy[i]=num[k];
i++;
}
}
for(k=low;k<=high;k++)
swap(num[k],copy[k]);
}
void merge_sort(int low,int high,vector<int>& num)
{
int mid;
if(low<high)
{
mid = low + (high-low)/2;
auto future1 = std::async(std::launch::deferred,[&]()
{
merge_sort(low,mid,num);
});
auto future2 = std::async(std::launch::deferred, [&]()
{
merge_sort(mid+1,high,num) ;
});
future1.get();
future2.get();
merge(low,mid,high,num);
}
}
Opmerking: In het voorbeeld wordt std::async
gestart met beleid std::launch_deferred
. Dit om te voorkomen dat bij elke oproep een nieuwe thread wordt gemaakt. In ons voorbeeld zijn de aanroepen naar std::async
buiten gebruik, ze synchroniseren bij de aanroepen voor std::future::get()
.
std::launch_async
dwingt bij elke oproep een nieuwe thread te maken.
Het standaardbeleid is std::launch::deferred| std::launch::async
, wat betekent dat de implementatie het beleid bepaalt voor het maken van nieuwe threads.
Async operatie klassen
- std :: async: voert een asynchrone bewerking uit.
- std :: future: biedt toegang tot het resultaat van een asynchrone bewerking.
- std :: belofte: verpakt het resultaat van een asynchrone bewerking.
- std :: packaged_task: bundelt een functie en de bijbehorende belofte voor het retourtype.