En informàtica, els futurs (futures), les promeses (promises), els retards (delays) i els diferits (deferreds) constitueixen abstraccions utilitzades per a la sincronització i la coordinació de l'execució en diversos llenguatges de programació concurrent. Cadascun d'aquests elements actua com un intermediari (proxy) d'un valor inicialment indeterminat, habitualment perquè el seu procés de càlcul encara no ha conclòs.
El terme promesa va ser introduït el 1976 per Daniel P. Friedman i David Wise, mentre que Peter Hibbard va emprar la denominació eventual. El terme futur aparegué un any més tard, el 1977, en un article de Henry Baker i Carl Hewitt.
Tot i que els termes futur, promesa, retard i diferit s'utilitzen sovint de manera intercanviable, en alguns contextos es diferencia entre futur i promesa. En aquesta distinció, un futur representa una vista de només lectura d'un valor pendent de càlcul, mentre que una promesa és un contenidor d'assignació única encarregat d'establir el valor corresponent. Un futur pot existir sense que se li associï immediatament una promesa concreta, i, tot i que diverses promeses potencials en podrien determinar el valor, només una pot fer-ho efectivament. En molts dissenys, futur i promesa es creen simultàniament: el futur actua com a valor de retorn i la promesa com a mecanisme de resolució. L'acció d'establir el valor final s'anomena habitualment resoldre, satisfer o vincular la promesa.
Aplicacions Els futurs i les promeses s'originaren en l'àmbit de la programació funcional i de paradigmes afins —com la programació lògica— amb l'objectiu de desacoblar l'obtenció d'un valor del procediment que el calcula, facilitant així la flexibilitat en l'execució i afavorint la paral·lelització del còmput. Posteriorment, aquestes abstraccions van adquirir rellevància en la computació distribuïda, on contribueixen a reduir la latència associada als cicles de comunicació (round trips) entre nodes. Finalment, han esdevingut un element fonamental en la programació asíncrona contemporània, ja que permeten expressar fluxos d'execució asíncrons en estil directe, evitant la necessitat de recórrer al pas de continuacions (continuation-passing style).
Encadenament de promeses (promise pipelining) L'ús de futurs pot disminuir substancialment la latència en sistemes distribuïts. Un mecanisme destacat és l'encadenament de promeses (promise pipelining), present en llenguatges com E i Joule, i denominat call-stream en Argus.
En crides remotes tradicionals, expressions com:
t1 := x.a(); t2 := y.b(); t3 := t1.c(t2);
requereixen una seqüència de comunicacions síncrones, cadascuna amb un missatge i la corresponent resposta. Si x, y, t1 i t2 resideixen en la mateixa màquina remota, calen dos cicles complets de xarxa abans de poder executar la tercera instrucció.
En canvi, amb futurs i amb la sintaxi del llenguatge E:
t1 := x <- a(); t2 := y <- b(); t3 := t1 <- c(t2);
les variables obtenen immediatament un futur com a valor de retorn i l'execució continua sense bloqueigs. Una implementació amb pipelining pot determinar t3 mitjançant un únic cicle de xarxa, ja que els missatges es poden enviar conjuntament i la màquina remota pot retornar un sol resultat compost.
Implícit i explícit L'ús de futurs pot adoptar una forma implícita —en què el futur actua com una referència ordinària i el llenguatge n'obté automàticament el valor— o explícita —en què cal una invocació específica, com ara el mètode get() de java.util.concurrent.Future en Java—. L'obtenció explícita s'anomena sovint forçat (forcing). Els futurs explícits es poden implementar completament com a biblioteques, mentre que la implementació de futurs implícits sol requerir suport nadiu del llenguatge.
L'article de Baker i Hewitt adoptava futurs implícits, adequats al model d'actors i a llenguatges orientats a objectes purs com Smalltalk. En canvi, Friedman i Wise descrivien únicament futurs explícits, probablement a causa de les restriccions del maquinari disponible en aquell moment per implementar futurs implícits de manera eficient.
Vistes de només lectura Diversos llenguatges —com Oz, E i AmbientTalk— permeten obtenir una vista de només lectura d'un futur: aquesta vista facilita l'accés al valor quan es resol, però no autoritza a resoldre'l. Aquesta característica s'alinea amb el principi de mínim privilegi, ja que restringeix la capacitat de resolució únicament als elements que l'han de dur a terme:
En C++ (des de C++11), std::future proporciona la vista de només lectura, mentre que std::promise estableix el valor corresponent. En Alice ML, el futur representa la vista de només lectura, i la promesa inclou el mecanisme de resolució. En .NET, System.Threading.Tasks.Task<T> actua com a vista de només lectura, mentre que TaskCompletionSource<T> permet resoldre el valor.
Semàntica de bloqueig i no-bloqueig L'accés asíncron al valor d'un futur —per exemple, a través de l'enviament de missatges— pot ajornar l'execució fins que el futur es resolgui sense dificultats conceptuals. Tanmateix, l'accés immediat o síncron pot adoptar diferents semàntiques:
Bloqueig: el fil d'execució es manté aturat fins que el futur adquireix un valor, sovint amb la possibilitat d'especificar un temps màxim d'espera (timeout). Aquest és el comportament de les variables de flux en Oz i dels mètodes wait() i get() de std::future en C++11. Error immediat: tot accés síncron a un futur no resolt genera immediatament una excepció. Aquesta és la semàntica emprada per les promeses remotes en E. Accés condicional: l'accés només té èxit si el futur ja ha estat resolt; en cas contrari, es produeix un error. Aquest model introdueix no-determinisme i risc de condició de carrera, i per això és poc habitual en la pràctica.
Semàntica en el model d'actors En el model d'actors, una expressió com future <Expressió> crea un actor F que actua com a substitut del valor final. Si F rep peticions abans de completar-se el càlcul, aquestes es guarden en una cua interna. Un cop obtingut el valor definitiu, F reemet totes les peticions pendents cap al resultat. Alguns futurs poden gestionar anticipadament certes operacions per maximitzar el paral·lelisme; per exemple, l'expressió 1 + future factorial(n) pot generar un nou futur que es comporti com 1 + factorial(n) sense requerir el resultat intermedi.
Història Els futurs i les promeses es van implementar inicialment en llenguatges com MultiLisp i Act 1. L'ús de variables lògiques com a mecanisme de comunicació en la programació lògica concurrent presentava una funcionalitat anàloga, amb antecedents en Prolog i desenvolupaments posteriors en Concurrent Prolog, Parlog i Oz-Mozart, entre d'altres.
La tècnica de l'encadenament de promeses va ser formalitzada per Barbara Liskov i Liuba Shrira el 1988, i, de manera independent, per Mark S. Miller, Dean Tribble i Rob Jellinghaus en el context del Projecte Xanadu cap al 1989. El terme promesa va ser encunyat per Liskov i Shrira, tot i que van denominar el mecanisme d'encadenament call-stream, un terme que avui dia rarament s'utilitza.
A partir dels anys 2000, l'interès per aquestes construccions experimentà un creixement notable, impulsat per la demanda d'interfícies d'usuari més responsives i pel model de petició-resposta del web. Entre les fites més destacades cal esmentar FutureTask en Java 5 (2004) i les construccions async/await de .NET 4.5 (2012), fortament inspirades en els fluxos de treball asíncrons de F# (2007). Posteriorment, el patró es va estendre a llenguatges com Dart (2014), Python (2015), Rust, Scala, JavaScript (ECMAScript 2015 i 2017) i C++ (des del 2011).
Implementacions destacades Entre els llenguatges i entorns que ofereixen suport nadiu o bibliotecari per a futurs i promeses s'inclouen:
C++: std::future i std::promise (C++11). Java: java.util.concurrent.Future i java.util.concurrent.CompletableFuture. JavaScript: objecte Promise (ECMAScript 2015) i sintaxi async/await (ECMAScript 2017). Python: mòdul concurrent.futures (Python 3.2) i sintaxi async/await (Python 3.5). Rust: std::future::Future amb resolució mitjançant .await. Scala: classes Future i Promise del paquet scala.concurrent. .NET: System.Threading.Tasks.Task amb les paraules clau async i await.
Referències
Vegeu també Programació concurrent Fil d'execució
Categoria:Programació