Futures und Promises stellen einen Mechanismus dar, Daten von einem Thread an einen anderen zu übergeben. Während dies auch zum Beispiel über globale Variablen bewerkstelligt werden kann, kommt die Datenübergabe mit Futures und Promises ohne globale Variablen aus. Darüberhinaus müssen Sie sich nicht selbst um eine Synchronisation kümmern.
Ein Future ist eine Variable, die einen Wert erhalten wird, der von einem anderen Thread berechnet wird. Wenn Sie auf ein Future zugreifen, um die Variable auszulesen, müssen Sie womöglich warten, bis der entsprechende Thread den Wert errechnet hat. Boost.Thread stellt den Typ boost::future
zur Verfügung, um ein Future zu definieren. Die Klasse bietet eine Methode get()
an, um den Wert der Variablen zu erhalten. Diese Methode ist blockierend, da sie unter Umständen auf einen Thread warten muss.
Um ein Future auf einen Wert zu setzen, muss auf ein mit dem Future verbundenes Promise zugegriffen werden. Während boost::future
eine Methode get()
besitzt, existiert keine Methode, um einen Wert zu setzen.
Für Promises stellt Boost.Thread die Klasse boost::promise
bereit. Diese Klasse besitzt eine Methode set_value()
. Der Trick ist, dass Futures und Promises als Paar auftreten. So kann über die Methode get_future()
ein Future von einem Promise erhalten werden. Future und Promise können in unterschiedlichen Threads verwendet werden. Wird das Promise in einem Thread gesetzt, kann der entsprechende Wert über den Future im anderen Thread gelesen werden.
boost::future
und boost::promise
in Aktion#define BOOST_THREAD_PROVIDES_FUTURE
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
#include <functional>
#include <iostream>
void accumulate(boost::promise<int> &p)
{
int sum = 0;
for (int i = 0; i < 5; ++i)
sum += i;
p.set_value(sum);
}
int main()
{
boost::promise<int> p;
boost::future<int> f = p.get_future();
boost::thread t{accumulate, std::ref(p)};
std::cout << f.get() << '\n';
}
Im Beispiel 44.14 kommen ein Future und ein Promise zum Einsatz. Das Future f wird über get_future()
vom Promise p erhalten. Daraufhin wird eine Referenz auf den Promise an den Thread t übergeben, der die Funktion accumulate()
ausführt. In accumulate()
wird die Summe der Zahlen 1 bis 5 errechnet und dann im Promise gespeichert. In main()
wird lediglich get()
für den Future aufgerufen, um den entsprechenden Wert in die Standardausgabe zu schreiben.
Das Future f und das Promise p sind verknüpft. Wenn für das Future get()
aufgerufen wird, wird der Wert zurückgegeben, der mit set_value()
im Promise gesetzt wurde. Da das Beispiel aus zwei Threads besteht, ist es möglich, dass get()
in main()
aufgerufen wird, bevor in acccumulate()
set_value()
aufgerufen wurde. In diesem Fall blockiert get()
und kehrt erst zurück, wenn mit set_value()
ein Wert im Promise gespeichert wurde.
Wenn Sie Beispiel 44.14 ausführen, wird 10
ausgegeben.
Das Beispiel erfordert, dass die Funktion accumulate()
für den Einsatz in einem Thread angepasst wurde. So muss die Funktion einen Parameter vom Typ boost::promise
erwarten und in diesem das Ergebnis speichern. Im folgenden Beispiel lernen Sie die Klasse boost::packaged_task
kennen, mit der Sie jede beliebige Funktion verwenden können, die das Ergebnis mit return
zurückgibt.
boost::packaged_task
in Aktion#define BOOST_THREAD_PROVIDES_FUTURE
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
#include <utility>
#include <iostream>
int accumulate()
{
int sum = 0;
for (int i = 0; i < 5; ++i)
sum += i;
return sum;
}
int main()
{
boost::packaged_task<int> task{accumulate};
boost::future<int> f = task.get_future();
boost::thread t{std::move(task)};
std::cout << f.get() << '\n';
}
Beispiel 44.15 ist vergleichbar mit dem vorherigen. Diesmal wird jedoch kein Promise vom Typ boost::promise
verwendet. Stattdessen kommt boost::packaged_task
zum Einsatz. Diese Klasse bietet wie boost::promise
eine Methode get_future()
an, um ein Future zu erhalten.
Dem Konstruktor von boost::packaged_task
muss eine Funktion übergeben werden, die in einem Thread ausgeführt werden soll. boost::packaged_task
selbst startet keinen Thread. Ein Objekt vom Typ boost::packaged_task
muss an den Konstruktor von boost::thread
übergeben werden, damit der Thread startet und die entsprechende Funktion im Thread ausgeführt wird.
Der Vorteil von boost::packaged_task
ist, dass der Rückgabewert der entsprechenden Funktion im Future gesetzt wird. Es ist nicht notwendig, eine Funktion dahingehend zu ändern, dass sie ein Ergebnis in einem Promise speichert. boost::packaged_task
ist in gewisser Weise ein Adapter, der den Rückgabewert einer Funktion in einem Future ablegt.
Während im obigen Beispiel boost::promise
weggelassen werden konnte, wird im folgenden zusätzlich auf boost::packaged_task
und boost::thread
verzichtet.
boost::async()
in Aktion#define BOOST_THREAD_PROVIDES_FUTURE
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
#include <iostream>
int accumulate()
{
int sum = 0;
for (int i = 0; i < 5; ++i)
sum += i;
return sum;
}
int main()
{
boost::future<int> f = boost::async(accumulate);
std::cout << f.get() << '\n';
}
Im Beispiel 44.16 wird accumulate()
an die Funktion boost::async()
übergeben. Diese Funktion vereint boost::packaged_task
und boost::thread
. Sie startet accumulate()
in einem Thread und gibt ein Future vom Typ boost::future
zurück.
Sie können boost::async()
eine Launch-Policy übergeben. Dieser zusätzliche Parameter bestimmt, ob boost::async()
die Funktion in einem neuen oder im aktuellen Thread ausführt. Wenn Sie boost::launch::async als ersten Parameter an boost::async()
übergeben, wird ein neuer Thread gestartet. Übergeben Sie boost::launch::deferred, wird die Funktion im aktuellen Thread ausgeführt. boost::launch::async ist der Standardwert, der gilt, wenn Sie keine Launch-Policy angeben.
In Boost 1.57.0 fehlt die Implementation für boost::launch::deferred. Übergeben Sie boost::launch::deferred an boost::launch::async, wird Ihr Programm sofort beendet.