std::promise und std::future geben die volle Kontrolle über die Task.
Volle Kontrolle
Ein std::promise erlaubt
- einen Wert, eine Benachrichtigung oder eine Ausnahme zu setzen. Diese kann auch erst zur Verfügung gestellt werden, wenn der aktuelle Thread beendet ist.
Ein std::future erlaubt
- den Wert des Promise abzuholen.
- den Promise zu fragen, ob der Wert bereits vorliegt.
- zu warten, bis eine Benachrichtigung des Promise vorliegt: Dies ist ohne und mit absoluter oder relativer Zeitangabe möglich. => Ersatz für Bedingungsvariablen
- einen geteilten Future (std::shared_future) zu erzeugen.
Sowohl der Promise als auch der Future können explizit in einen anderen Thread verschoben werden. Damit findet die Kommunikation über Threadgrenzen hinweg statt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#include <future>
#include <iostream>
#include <thread>
#include <utility>
void product(std::promise<int>&& intPromise, int a, int b){
intPromise.set_value(a*b);
}
struct Div{
void operator() (std::promise<int>&& intPromise, int a, int b) const {
intPromise.set_value(a/b);
}
};
int main(){
int a= 20;
int b= 10;
std::cout << std::endl;
// define the promises
std::promise<int> prodPromise;
std::promise<int> divPromise;
// get the futures
std::future<int> prodResult= prodPromise.get_future();
std::future<int> divResult= divPromise.get_future();
// calculate the result in a separat thread
std::thread prodThread(product,std::move(prodPromise),a,b);
Div div;
std::thread divThread(div,std::move(divPromise),a,b);
// get the result
std::cout << "20*10= " << prodResult.get() << std::endl;
std::cout << "20/10= " << divResult.get() << std::endl;
prodThread.join();
divThread.join();
std::cout << std::endl;
}
|
Der Thread prodThread (Zeile 34) verwendet die Funktion product (Zeile 6 - 8), den prodPromise (Zeile 30) und die Zahlen a und b. Um die Argumente des Threads prodThread zu verstehen, ist es notwendig, die Signatur der Funktion genauer zu betrachten. prodThread erwartet als erstes Argument eine aufrufbare Einheit. Dies ist die bereits zitierte Funktion product. product erwartet eine Rvalue Referenz (std::promise<int>&& intPromise) auf einen Promise, der eine ganze Zahl zurückgibt. Genau dies entspricht den drei letzen Argumenten des Threads prodThread. Die von der Funktion product benötigte Rvalue Referenz erzeugt der std::move-Aufruf in Zeile 34. Der Rest ist schnell erklärt. Der Thread divThread (Zeile 36) teilt die beiden Zahlen a und b. Dazu verwendet er die Instanz der Klasse Div (Zeile 10 - 16) div. div ist ein Funktionsobjekt.
Durch die Aufrufe prodResult.get() und divResult.get() sammeln die Future die Ergebnisse ein.
Per Default besteht zwischen dem Promise und dem Future eine eins-zu-eins Beziehung. std::shared_future erlauben eins-zu-viele Beziehung zwischen Promise und Future.
std::shared_future
Ein std::shared_future
- kann unabhängig vom den anderen assoziierten Future seinen Promise abfragen.
- besitzt das gleiche Interface wie ein std::future.
- kann durch einen std::future fut mit dem Aufruf fut.share() erzeugt werden.
- kann durch einen std::promise divPromise mit dem Aufruf std::shared_future<int> divResult= divPromise.get_future() erzeugt werden.
Der Umgang mit std::shared_future ist ein bisschen besonders.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
#include <exception>
#include <future>
#include <iostream>
#include <thread>
#include <utility>
std::mutex coutMutex;
struct Div{
void operator()(std::promise<int>&& intPromise, int a, int b){
try{
if ( b==0 ) throw std::runtime_error("illegal division by zero");
intPromise.set_value(a/b);
}
catch (...){
intPromise.set_exception(std::current_exception());
}
}
};
struct Requestor{
void operator ()(std::shared_future<int> shaFut){
// lock std::cout
std::lock_guard<std::mutex> coutGuard(coutMutex);
// get the thread id
std::cout << "threadId(" << std::this_thread::get_id() << "): " ;
// get the result
try{
std::cout << "20/10= " << shaFut.get() << std::endl;
}
catch (std::runtime_error& e){
std::cout << e.what() << std::endl;
}
}
};
int main(){
std::cout << std::endl;
// define the promises
std::promise<int> divPromise;
// get the futures
std::shared_future<int> divResult= divPromise.get_future();
// calculate the result in a separat thread
Div div;
std::thread divThread(div,std::move(divPromise),20,10);
Requestor req;
std::thread sharedThread1(req,divResult);
std::thread sharedThread2(req,divResult);
std::thread sharedThread3(req,divResult);
std::thread sharedThread4(req,divResult);
std::thread sharedThread5(req,divResult);
divThread.join();
sharedThread1.join();
sharedThread2.join();
sharedThread3.join();
sharedThread4.join();
sharedThread5.join();
std::cout << std::endl;
}
|
Sowohl die Arbeitspakete des Promise als auch des Futures sind in diesem Beispiel Funktionsobjekte. Bei der der Division zweier Zahlen besteht immer die Gefahr, dass der Nenner 0 ist. Dies führt natürlich zu einer Ausnahme. Mit dieser Gefahr geht der Promise um, in dem er alle Ausnahmen fängt (Zeile 16 -18) und an den Future weiter wirft. Der std::future nimmt diese Ausnahmen an und gibt sie in Zeile 38 aus. In Zeile 56 wird der divPromise in den divThread geschoben und ausgeführt. Analog wird der std::shared_future in die fünf Threads kopiert. Ich betone es gerne nochmals. Im Gegensatz zu std::future-Objekten, die nur verschoben werden können, können std::shared_future-Objekte auch kopiert werden.
In den Zeilen 65 bis 71 wartet der main-Thread auf seine Kinderthreads und gibt die Ergebnisse aus.
Hintergrundinformation
- std::promise
- Die Details lassen sich schön auf cppreference nachlesen.
- std::future
- Die Details lassen sich schön auf cppreference nachlesen.
-
Wie geht's weiter?
std::async erzeugt besondere Futures. Diese unterscheiden sich von den Futures, die ein std::promise oder ein std::package_task erzeugt dadurch, dass sie im Destruktor blockieren, bis ihr assoziierter Promise mit seinem Arbeitspaket fertig ist. Die Details zu diesen besonderen Futures folgen im nächsten Artikel.
-
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...