Versprechen und Zukunft

Versprechen und Zukunft

Mit std::promise und std::future haben Sie die volle Kontrolle über die Aufgabe.

Volle Kontrolle über die Aufgabe

Ein std::promise erlaubt

  • um einen Wert, eine Benachrichtigung oder eine Ausnahme festzulegen. Dieses Ergebnis kann zusätzlich durch das Versprechen verzögert werden.

Ein std::future erlaubt dies

  • Abholung des Wertes aus dem Versprechen.
  • fragt das Promise, ob der Wert verfügbar ist.
  • Warten Sie auf die Benachrichtigung über das Versprechen. Dieses Warten kann mit einer relativen Zeitdauer oder einem absoluten Zeitpunkt erfolgen. => Ersatz für Bedingungsvariablen.
  • Schaffe eine gemeinsame Zukunft (std::shared_future).

Beide Kommunikationsendpunkte verspricht und kann künftig in einen eigenen Thread verschoben werden. Die Kommunikation findet also zwischen Threads 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
49
50
// promiseFuture.cpp

#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 36) verwendet die Funktion product (Zeile 8-10), die prodPromise (Zeile 32) und die Zahlen a und b. Um die Argumente des Threads prodThread zu verstehen, müssen Sie sich die Signatur der Funktion ansehen. prodThread benötigt als erstes Argument ein Callable. Dies ist das bereits erwähnte Funktionsprodukt. product benötigt ein Promise der Art rvalue reference (std::promise&&intPromise) und zwei Zahlen. Dies sind genau die letzten drei Argumente des Threads prodThread. std::move in Zeile 36 erstellt die rvalue-Referenz. Der Rest ist ein Kinderspiel. Der Thread divThread (Zeile 38) dividiert die beiden Zahlen a und b. Da es sich um einen Job handelt, verwendet er die Instanzen div der Klasse Div (Zeile 12 - 18). div ist ein Funktionsobjekt.

Der Future holt sich die Ergebnisse durch die Aufrufe prodResult.get() und divResult.get().

Standardmäßig besteht eine Eins-zu-Eins-Beziehung zwischen dem Versprechen und der Zukunft. Aber std::shared_future unterstützt eine Eins-zu-Viele-Beziehung zwischen einem Versprechen und vielen Zukünften.

std::shared_future

Ein std::shared_future

  • erlaubt Ihnen, das Versprechen unabhängig von den anderen zugehörigen Futures zu stellen.
  • hat dieselbe Schnittstelle wie ein std::future.
  • kann von einem std::future fut mit dem Aufruf fut.share() erzeugt werden.
  • kann von einem std::promise divPromise mit dem Aufruf std::shared_future divResult=divPromise.get_future() erzeugt werden.

Die Verwaltung von std::shared_future ist etwas Besonderes.

 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
76
77
// sharedFuture.cpp

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

}

Beide Arbeitspakete des Versprechens und der Zukunft sind in diesem aktuellen Beispiel Funktionsobjekte. Wenn Sie in Zahlen dividieren, müssen Sie auf den Nenner achten. Es darf nicht 0 sein. Wenn es 0 ist, erhalten Sie eine Ausnahme. Das Versprechen behandelt dieses Problem, indem es die Ausnahme abfängt (Zeile 18 - 20) und sie in die Zukunft zurückwirft. std::future fängt die Ausnahme ab und zeigt sie in Zeile 40 an. In Zeile 58 wird divPromise verschoben und in divThread ausgeführt. Dementsprechend werden die std::shared_future's kopiert in den fünf Threads. Ich werde dies noch einmal betonen. Im Gegensatz zu einem std::future-Objekt, das nur verschoben werden kann, können Sie ein std::shared_future-Objekt kopieren.

Der Haupt-Thread wartet in den Zeilen 69 bis 73 auf seine Kinder und zeigt die Ergebnisse an.

Was kommt als nächstes?

Es gibt eine Kuriosität bei std::async, die Sie kennen sollten. Das by std::async erstellte zukünftige Blöcke in seinem Destruktor, bis das zugehörige Promise abgeschlossen ist. Neugierig? Lesen Sie den nächsten Beitrag.