Future 和 Promise
从异步与并发编程兴起以来,学术界与工业界提出了非常多的解决方案,本文将要介绍的 Future 和 Promise 正是其中的两种解决方案。Future 和 Promise 的实现理念非常相似,两者在发展过程中相互借鉴,相互融合。目前,很多流行的语言和框架都引入了 Future 和 Promise 的概念,如:JavaScript、Node.js、Scala、Java、C++ 等。
本文,我们来简单聊一聊 Future 和 Promise 历史和设计,以及两者之间的关系与区别。
历史简介
1962. Thunk
关于 Future 和 Promise 的起源,最早可以追溯到 1961 年的 Thunk。根据创造者 P.Z. Ingerman 的描述,Thunk 是提供地址的一段代码。
Thunk 被设计为一种将实际参数绑定到 Algol-60 过程调用中的正式定义的方法。如果用表达式代替形式参数调用过程,编译器会生成一个 thunk,它将执行表达式并将结果的地址留在某个标准位置。
目前,thunk 的用法仍然非常广泛。
1977. Future
1977 年,Henry C. Baker 和 Hewitt 在论文《The Incremental Garbage Collection of Process》中首次提到 Future。
他们提出了一个新的术语 call-by-future
,用于描述一种基于 Future 的调用形式。当将表达式提供给执行器时,将返回该表达式的 .future
。如果表达式返回类型为值类型,那么当未来表达式计算得到值时,会将值返回。这里会为每一个 future
都会创建一个进程,并立即执行表达式。如果表达式已完成,则值立即可用;如果表达式未完成,则请求进程等待表达式执行完成。
在论文中,Future 主要由三部分组成:
- 进程(Process):用于执行表达式的进程。
- 单元(Cell):可写入值的内存地址,用于存储表达式的未来值。
- 队列(Queue):等待未来值的进程列表。
从 Future 的概念我们可以看出,论文所提到的 Future 几乎已经和现代的 Future 概念非常接近了。
1985. Multilisp
1985 年,Robert H. Halstead 在论文《Multilisp: A Language for Concurrent Symbolic Computation》中提出的 Multilisp 语言支持了基于 future
注解的 call-by-future
能力。
在 Multilisp 中,如果变量绑定到 Future 的表达式,则会自动创建一个新的进程。表达式会在新的进程中执行,一旦执行完成,则将计算结果保存至变量引用中。通过这种方式,Multilisp 支持在新进程中同时计算任意表达式的能力。因此,也支持无需等待 Future 完成,继续执行其他计算的能力。这样的话,如果 Future 的值从未使用过,那么整个进程就不会被阻塞,从而消除了潜在的死锁源。
相比于 1977 年提出的 Future,Mutilisp 实现的 Future 支持在特定情况下不阻塞进程,从而一定程度上优化了程序的执行效率。
1988. Promise
1988 年,Liskov 和 Shrira 在论文《Distributed Programming in Argus》中提出的 Argus 语言设计了一种称为 Promises 的结构。
与 Multilisp 中的 Future 类似,Argus 中的 Promise 也提供一个用于存储未来值的占位符。Promise 的特别之处在于,当调用 Promise 时,会立即创建并返回一个 Promise,并在新进程中进行类型安全的异步 PRC 调用。当异步 PRC 调用执行完毕,由调用者设置返回值。
设计理念
经过数十年的发展,Future 和 Promise 的设计理念整体上非常相似,但是在不同的语言和框架实现中又存在一定的区别,对此,这里我们基于最广泛的定义进行介绍。
整体实现
在 Scala、C++ 等编程语言中,同时包含两种结构分别对应 Future 和 Promise。作为整体实现,Future 和 Promise 可被视为同一异步编程技术中的两个部分:
- Future:表示异步任务的 返回值,表示一个未来值的占位符,即 值的消费者。
- Promise:表示异步任务的 执行过程,表示一个值的生产过程,即 值的生产者。
在同时包含 Future 和 Promise 的实现中,一般 Promise 对象会有一个关联的 Future 对象。当 Promise 创建时,Future 对象会自动实例化。当异步任务执行完毕,Promise 在内部设置结果,从而将值绑定至 Future 的占位符中。Future 则提供读取方法。
将异步操作分成 Future 和 Promise 两个部分的主要原因是 为了实现读写分离,对外部调用者只读,对内部实现者只写。
下面,我们以几种语言中的实现来分别进行介绍。
C++ Future & Promise
在 C++ 中,Future 和 Promise 是一个异步操作的两个部分。
std::future
:作为异步操作的消费者。std::promise
:作为异步操作的生产者。
auto promise = std::promise<std::string>(); auto producer = std::thread([&] { promise.set_value("Hello World"); }); auto future = promise.get_future(); auto consumer = std::thread([&] { std::cout << future.get(); }); producer.join(); consumer.join();
从上述代码中可以看出,C++ Promise 包含了 Future,可以通过 get_future
方法获取 Future 对象。两者有明确的分工,Promise 提供了 set_value
方法支持写操作,Future 提供了 get
方法支持读操作。
Scala Future & Promise
在 Scala 中,同样如此,Future 和 Promise 可作为同一个异步操作的两个部分。
Future
作为一个可提供只读占位符,用于存储未来值的对象。Promise
作为一个实现一个 Future,并支持可写操作的单一赋值容器。
import scala.concurrent.{ Future, Promise } import scala.concurrent.ExecutionContext.Implicits.global val p = Promise[T]() val f = p.future val producer = Future { val r = produceSomething() p success r continueDoingSomethingUnrelated() } val consumer = Future { startDoingSomething() f onSuccess { case r => doSomethingWithResult() } }
从上述代码中可以看出,Scala Promise 同样包含了 Future,可以通过 future
属性获取 Future 对象。Promise 提供了 success
、 failure
等方法来更新状态。Future 提供了 onSuccess
、 onFailure
等方法来监听未来值。
独立实现
其他很多编程语言中,并不同时包含 Future 和 Promise 两种结构,比如:Dart 只包含 Future,JavaScript 只包含 Promise,甚至有些编程语言混淆了 Future 和 Promise 的原始区别。
在独立实现中,Future 和 Promise 各自都有着相对比较统一的表示形式,在实现方面的差异也相对比较一致,主要包括以下几个方面区别:
- 状态表示
- 状态更新
- 返回机制
状态表示
在状态表示方面,Future 只有两种状态:
uncomplete
:表示未完成状态,即未来值还未计算出来。completed
:表示已完成状态,即未来值已经计算出来。当然计算结果可以分为值或错误两种情况。
对于 Promise,一般使用三种状态进行表示:
pending
:待定状态,即 Promise 的初始状态。fulfilled
:满足状态,表示任务执行成功。rejected
:拒绝状态,表示任务执行失败。
无论是 Future 还是 Promise,状态转移的过程都是不可逆的。
状态更新
在状态更新方面,Future 的状态由 内部进行自动管理。当异步任务执行完成或抛出错误时,其状态将隐式地自动从 uncomplete
状态更新为 completed
状态。
对于 Promise,其状态由 外部进行手动管理。通常由开发者根据控制流逻辑,执行特定的状态更新方法显式地从 pending
状态更新为 fulfilled
或 rejected
状态。
返回机制
在返回机制方面,Future 以传统的 return
方式返回结果。如下所示为 Dart 中 Future 的返回机制示例,其返回正如普通的方法一样,通过 return
完成。
Future<String> _readFileAsync() async { final file = File(filename); final contents = await file.readAsString(); return contents.trim(); }
而 Promise 通常将结果作为闭包参数进行传递,并执行闭包从而实现返回。如下所示为 JavaScript 中 Promise 的返回机制示例, resolve
是一个只接受成功值的闭包,其参数为 Image
类型; reject
是一个只接受错误值的闭包,其参数为 Error
类型。
function loadImageAsync(url) { return new Promise(function(resolve, reject) { const image = new Image(); image.onload = function() { resolve(image); }; image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; image.src = url; }); }
语言实现
下面,我们来看一下各种编程语言是如何独立实现 Future 或 Promise 的。
Dart
Dart 内置提供了标准 Future
实现,其同时提供了 async
和 await
关键字分别用于描述异步函数和等待异步函数。如下所示,为 Dart 中的 Future 应用示例。
Future<String> createOrderMessage() async { var order = await fetchUserOrder(); return 'Your order is: $order'; } Future<String> fetchUserOrder() { return Future.delayed( const Duration(seconds: 2), () => 'Large Latte', ); }
C
C# 提供了 Task
,其本质上类似于一种 Future 实现。此外,C# 还提供了异步函数关键字 async
和 await
,分别用于描述异步函数和等待异步函数。如下所示,为 C# 中的使用示例。
async Task<int> AccessTheWebAsync() { HttpClient client = new HttpClient(); Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); DoIndependentWork(); string urlContents = await getStringTask; return urlContents.Length; } string urlContents = await client.GetStringAsync();
Swift
Swift 提供了 Task
,其本质是一种加强版的 Future 实现。Swift 通过提供额外的 TaskGroup
的概念,使其同时支持结构化并发和非结构化并发。此外,Swift 也提供的 async
await
关键字支持异步函数,基于此,Swift 也能够实现和其他语言一样的 Future 实现。如下所示,为 Swift 中类似于 Future 的使用示例。
let newPhoto = // ... some photo data ... let handle = Task { return await add(newPhoto, toGalleryNamed: "Spring Adventures") } let result = await handle.value
Java
Java 1.5 提供了 Future
和 FutureTask
,其中 Future
是一个接口, FutureTask
是一种实现,它们提供了一种相对标准的 Future 实现。其通过 Runnable
和 Callable
进行实例化,有一个无参构造器, Future
和 FutureTask
支持外部只读, FutureTask
的 set 方法是 protected
,未来值只能由内部进行设置。如下所示,为基于 FutureTask
的一个应用示例。
public class Test { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); Task task = new Task(); FutureTask<Integer> futureTask = new FutureTask<Integer>(task); executor.submit(futureTask); executor.shutdown(); try { System.out.println("task result: "+ futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
Java 8 提供了 CompletableFuture
,其本质上是一种 Promise 的实现。按照我们之前的定义,Future 是只读的,Promise 是可写的,而 CompletableFuture
提供了可由外部调用的状态更新方法,因此可以将其归类为 Promise。另一方面, CompletableFuture
又实现了 Future 的读取方法 get
。整体上, CompletableFuture
混合了 Future 和 Promise 的能力。如下所示,为 CompletableFuture
的一个应用示例。
Supplier<Integer> momsPurse = ()-> { try { Thread.sleep(1000);//mom is busy } catch (InterruptedException e) { ; } return 100; }; ExecutorService ex = Executors.newFixedThreadPool(10); CompletableFuture<Integer> promise = CompletableFuture.supplyAsync(momsPurse, ex); promise.thenAccept(u->System.out.println("Thank you mom for $" + u )); promise.complete(10);
JavaScript
从 ES6 开始,JavaScript 支持了 Promise 的经典实现,同时支持了 async
和 await
关键字用于描述异步任务。使用 async
关键字修饰函数的返回值是一个 Promise
对象。 await
关键字修饰一个 Promise
对象,表示等待异步任务的值,有点类似等待 Future。如下所示,为 JavaScript 中 Promise
的使用示例。
class Sleep { constructor(timeout) { this.timeout = timeout; } then(resolve, reject) { const startTime = Date.now(); setTimeout( () => resolve(Date.now() - startTime), this.timeout ); } } (async () => { const sleepTime = await new Sleep(1000); console.log(sleepTime); })();
总结
本文简单介绍了一下 Future 和 Promise 的发展历史。然后,分别介绍了两者在实现中的关系和区别。同时,介绍了 Future 和 Promise 在各种编程语言中的实现。
后续有时间,我们在来深入研究一下编程语言层面是如何支持 Future 和 Promise 。
参考
- Multilisp: A Language for Concurrent Symbolic Computation
- The Incremental Garbage Collection of Process
- Futures and Promises
- Futures and Promises
- Future 和 Promise 的区别
- What's the difference between a Future and a Promise?
- Futures and Promises
- Under the hood of Futures and Promises in Swift
- Futures vs. Promises
- What is std::promise?
- std::promise
- threads, promises, futures, async, C++
- FUTURE 和 PROMISE
- Java 并发编程:callable、Future 和 FutureTask
- Class FutureTask
- ECMAScript 6 入门
- Netty Promise
- Netty 中的异步编程 Future 和 Promise
- So what's a thunk?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论