Ein Future (engl. ‚Zukunft‘) oder ein Promise (engl. ‚Versprechen‘) bezeichnet in der Programmierung einen Platzhalter (Proxy) für ein Ergebnis, das noch nicht bekannt ist, meist weil seine Berechnung noch nicht abgeschlossen ist.
Ein Future ist meist das Ergebnis eines asynchronen Aufrufs einer Funktion oder einer Methode und kann verwendet werden, um auf das Ergebnis zuzugreifen, sobald es verfügbar ist. Diese Art der Programmierung erlaubt eine weitgehend transparente Parallelisierung nebenläufiger Prozesse. Das Konzept der Futures wurde 1977 in einem Artikel von Henry G. Baker und Carl Hewitt vorgestellt.
Eine zentrale Idee der Programmierung mit Futures ist, dass Futures als Argumente an andere Prozeduraufrufe weitergereicht werden können. Die Auswertung dieses Aufrufs kann dann schon beginnen, bevor das Ergebnis des Futures selbst verfügbar ist. Das erlaubt ein maximales Maß an Parallelismus. Erfolgt der neue Aufruf wiederum asynchron, so spricht man auch von Pipelining der Futures. Pipelining kann insbesondere in verteilten Anwendungen benutzt werden, um die Latenzzeiten von Interprozesskommunikation zu minimieren.
Futures sind ein Konstrukt zur asynchronen Interprozesskommunikation. Konzeptionell bietet ein Future eine get- oder join-Funktion, die so lange blockiert, bis das Ergebnis vorliegt, und dieses dann zurückliefert. Je nach Implementierung kann die Wartezeit mittels Timeout beschränkt werden oder durch zusätzliche Funktionen eine Abfrage des aktuellen Status erfolgen.
Sind Futures direkt in die Programmiersprache integriert, so ist häufig nur ein asynchroner Zuweisungsoperator definiert, zum Beispiel x @= Ausdruck in Flow Java, einer an Java angelehnten experimentellen Programmiersprache. Dies bedeutet: Starte einen Prozess zum Berechnen des Ausdrucks, der rechts des Operators steht, und weise der Variable x ein Future für das Ergebnis zu. Wird danach auf die Variable x zugegriffen, so wird an dieser Stelle so lange gewartet, bis das Ergebnis vorliegt.
Programmiersprachen und Programmbibliotheken, die Futures oder Promises unterstützen sind CORBA (mit Asynchronous Method Invocation (AMI)), und – ab Version 5 – Java mittels Concurrency Utilities, einer Klassenbibliothek für Nebenläufigkeit. JavaScript stellt diese Konstrukte seit ECMAScript 6 bereit (wenngleich derzeit noch in eingeschränkter Form.)[1] Rust stellt ebenfalls Futures zur Verfügung. Auch für C++ stehen verschiedene Bibliotheken zur Verfügung, die wohl bekannteste von ihnen ist Boost. Im Standard C++11 sind Nebenläufigkeit und Futures ebenfalls in der Standardbibliothek verfügbar. Weitere Programmiersprachen mit Unterstützung für Futures und Promises sind Io, Oz, Scheme, Smalltalk und Scala.
In C# 5.0 und Visual Basic 2013 werden Futures über async und await implizit verwendet.[2] Eine entsprechende Future-Klasse ist hierbei in den Parallel Extensions definiert und kann somit auch in älteren Versionen und anderen Programmiersprachen verwendet werden. Eine solche kann bei Bedarf allerdings auch selbst implementiert werden.[3][4][5]
Ein Future ist eine Monade mit einem zugehörigen Resolver, welcher dem Future einen Wert zuweist. Das Future kann sich in einem von drei Zuständen befinden:
Wenn der Wert vom Resolver zugewiesen und damit bekannt ist, generiert das Future ein Ereignis für welche Rückruffunktionen registriert werden.
Der folgende Pseudocode zeigt die Verwendung von Futures mittels des asynchronen Zuweisungsoperators @=.
var x @= berechneX(); // Beginne Berechnung von x var y @= berechneY(); // Beginne Berechnung von y
var z = berechneZ(); // Vollständige Berechnung von z
var ergebnis= x + y + z; // Benutze x, y und z. // Hier muss dann eventuell auf die Berechnung // von x und y gewartet werden.
Daraus ergibt sich folgende Parallelisierung:
Haupt-Thread | X-Thread | Y-Thread |
---|---|---|
starte berechneX() | ||
starte berechneY() | berechneX() | |
berechneZ() | berechneY() | |
warte auf x und y | ||
berechne ergebnis |
Die Aufteilung der Berechnung auf mehrere Threads kann Berechnungen deutlich beschleunigen, wenn mehrere Hauptprozessoren (oder Prozessorkerne) zur Verfügung stehen, oder wenn die einzelnen Berechnungen den Hauptprozessor nicht auslasten, weil sie etwa viel Zeit mit dem Warten auf Peripheriegeräte verbringen.