Promise und Future

Inhaltsverzeichnis[Anzeigen]

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 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.

promiseFuture

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. Beider 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, indem er alle Ausnahme 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, der nur verschoben werden kann, 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.

sharedFuture

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.

Hintergrundinformation

std::promise   
    Die Details lassen sich schön auf cppreference nachlesen.
std::future   
    Die Details lassen sich schön auf cppreference nachlesen.

 

 

 

 

 

 

title page smalltitle page small 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.

Kommentar schreiben


Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare