Future与promise

计算机科学中,futurepromisedelaydeferred是指用于在某些并发编程语言中同步程序执行的构造。由于某些计算(或者网络请求)尚未结束,我们需要一个对象来代理这个未知的结果,于是就有了上述这些构造(future、promise等)。

promise一词由丹尼尔·福瑞得曼和David Wise在1976年提出,[1] Peter Hibbard称之为eventual[2] 1977年Henry Baker和Carl Hewitt在一篇论文中介绍了一个类似的概念future[3]

术语futurepromisedelaydeferred通常可以互换使用,而futurepromise之间的使用差异,我们将在下面讨论。具体来说,当区分使用时,future是变量的只读占位符视图,而promise是可写的单赋值容器,用于设置future的值。 [4] 值得注意的是,无须指定可以设置其值的promise就可以定义future,并且不同的promise可以设置同一个future的值,尽管对于给定的future仅可以执行一次。在其他情况下,future和promise是一起创建的,并且相互关联:future是值,promise是设定值的函数——本质上是异步函数(promise)的返回值(future)。设置future的值的过程也称为resolve(解析)、fulfil(实现)或bind(绑定)它。

应用

future和promise起源于函数式编程和相关范例(如逻辑编程 ),目的是将值(future)与其计算方式(promise)分离,从而允许更灵活地进行计算,特别是通过并行化。后来,它在分布式计算中得到了应用 ,减少了通信往返的延迟。再后来,它变得更有用了,因为它允许以直接风格编写异步程序,而不是以连续传递风格的方式。

隐式与显式

对future的使用可能是隐式的(任何对future的使用都会自动获得它的值,它就像是普通的引用一样)或显式的(用户必须调用函数来获取值,例如Java中的java.util.concurrent.Futurejava.util.concurrent.CompletableFutureget方法)。获得一个显式的future的值可以称为stingingforcing。显式future可以作为库来实现,而隐式future则通常作为语言的一部分来实现。

最初的Baker和Hewitt论文描述了隐式future,它们在演员模型和纯面向对象编程语言(如Smalltalk)中自然得到支持。Friedman和Wise的论文只描述了显式的future,可能反映了在老旧硬件上有效实施隐式future的困难。难点在于老旧硬件不能处理原始数据类型(如整数)的future。例如,add指令不会处理3 + future factorial(100000) 。在纯演员模型或面向对象语言中,这个问题可以通过发送future factorial(100000)消息+[3],它要求future自己加3并返回结果。请注意,无论factorial(100000)何时完成计算,消息传递方法都可以工作,而且不需要任何sting或force。

promise流水线

分布式系统中使用future可以显著地减少延迟。例如,future让promise流水线成为了可能,[5][6] 就像在E语言和Joule语言中实现的那样,在Argus语言中这也被称为call-stream[7]

考虑一个涉及常规远程过程调用的表达式,例如:

 t3 := ( x.a() ).c( y.b() )

可以扩展为

 t1 := x.a();
 t2 := y.b();
 t3 := t1.c(t2);

每个语句需要发送一条消息,并在下一个语句可以继续之前收到一个答复。例如,假设xyt1t2都位于同一台远程机器上。在这种情况下,在开始执行第三条语句之前,必须对该机器进行两次完整的网络往返。然后,第三条语句将引起另一个到同一个远程机器的往返。

使用future,上面的表达式可以写成

 t3 := (x <- a()) <- c(y <- b())

可以扩展为

 t1 := x <- a();
 t2 := y <- b();
 t3 := t1 <- c(t2);

这里使用的语法是E语言的语法,其中x <- a()表示将消息a()异步发送给x。所有三个变量都会立即为其结果分配future,执行过程将继续进行到后面的语句。之后尝试解决t3的值可能会导致延迟;但是,流水线操作可以减少所需的往返次数。如果与前面的示例一样,xyt1t2都位于相同的远程机器上,则流水线实现可以用一次往返来计算t3,不必用三次。由于所有三条消息都指向同一远程计算机上的对象,因此只需要发送一个请求,只需要接收一个包含结果的响应。另请注意,即使t1t2位于不同机器上,或者位于与xy不同的机器上,发送t1 <- c(t2)也不会阻塞。

promise流水线应与并行异步消息传递区分开来。在支持并行消息传递但不支持流水线操作的系统中,上面示例中的消息发送x <- a()y <- b()可以并行进行,但发送t1 <- c(t2)将不得不等到t1t2都被接收,即使xyt1t2在同一个远程机器上。在涉及许多消息的更复杂情况下,流水线的相对延迟优势变得更大。

promise流水线操作也不应与演员系统中的流水线消息处理相混淆,在这种系统中,可以演员在完成当前消息的处理之前指定并开始执行下一个消息的行为。

只读视图

在某些编程语言(如Oz\E和AmbientTalk)中 ,可以获得未来的只读视图 ,该视图允许在resolve后读取其值,但不允许resolve它:

  • 在Oz语言中,

!!运算符用于获得只读视图

  • 在E语言和AmbientTalk中,future由一对称为promise/resolver对的值表示。promise表示只读视图,需要resolver来设置future的值。
  • 在C++11中,std::future提供了一个只读视图。该值通过使用std::promise直接设置,或使用std::packaged_taskstd::async设置为函数调用的结果。
  • 在Dojo Toolkit的1.5版本的Deferred API中, 仅限consumer的promise对象表示只读视图。[8]
  • 在Alice ML中,future提供只读视图 ,而promise包含future和resolve future的能力[9][10]
  • 在.NET 4.0中,System.Threading.Task.Task<T>表示只读视图。解析值可以通过System.Threading.Task.TaskCompletionSource<T>来完成。

对只读视图的支持符合最小特权原则,因为它允许将值设置为仅限于需要设置该值的主体。在同样支持流水线的系统中,异步消息的发送方(包括结果)接收结果的只读承诺promise,消息的目标接收resolver。

针对特定线程的future

某些语言(如Alice ML )定义了与计算future值的特定线程相关联的future。[10] 这种计算可以在创建future时及早开始,或者在首次需要其值时懒惰地开始。在延迟计算的意义上,懒惰的future类似于thunk 。

Alice ML还支持可由任何线程解决的future,并调用这些promise[9] promise的这种使用不同于上文所述的在E语言中的使用。在Alice中,promise不是只读视图,并且不支持promise流水线操作。相反,对于future未来,包括与promise相关的future,流水线是自然而然地发生的。

阻塞与非阻塞语义

如果future的值是异步访问的,例如通过向它发送消息,或者通过使用类似于E语言中的when的构造显式地等待它,那么在收到消息或完成等待之前,推迟到future得到resolve没有任何困难。这是在纯异步系统(如纯演员语言)中唯一需要考虑的情况。

然而,在某些系统中,还可能尝试立即同步访问未来的值。这样的话就需要做出一个设计选择:

  • 访问权限可能会阻塞当前线程或进程,直到future得到resolve(可能需要超时)。这是Oz语言中数据流变量的语义。
  • 尝试同步访问总是会发出错误信号,例如抛出异常 。这是E语言中远程promise的语义。 [11]
  • 如果future已经resolve,则访问可能成功,但如果未resolve,则发出错误信号。这样做的缺点是引入了不确定性和潜在的竞争条件,这似乎是一种不常见的设计选择。

作为第一种可能性的示例,在C++11中 ,需要future值的线程可以通过调用wait()get()成员函数来阻塞,直到可用为止。您还可以使用wait_for()wait_until()成员函数指定等待超时,以避免无限期阻塞。如果future对std::async的调用,那么阻塞等待(没有超时)可能导致函数的同步调用以计算等待线程上的结果。

相关结构

future是Event(同步原语)的特例,只能完成一次。通常,event可以重置为初始空状态,因此可以根据需要多次完成。[12]

I-var(如在语言Id中)是具有上面定义的阻塞语义的future。I-structure是包含I-var的数据结构。可以使用不同值多次设置的相关同步构造称为M-var。M-var支持采用放置当前值的原子操作,其中取值还将M-var设置回其初始状态。 [13]

并发逻辑变量与future类似,但是通过合一更新,与逻辑编程中的逻辑变量相同。因此,它可以多次绑定到可合一的值,但不能设置回空或unresolved状态。Oz的数据流变量充当并发逻辑变量,并且还具有上面提到的阻塞语义。

并发约束变量是并发逻辑变量的一般化,以支持约束逻辑编程:约束可以多次缩小,表示可能值的较小集合。通常,有一种方法可以指定每当约束进一步缩小时应该运行的hunk;这是支持约束传播所必需的。

不同形式future的表达能力之间的关系

通过在创建future的同时创建一个计算值的线程,可以在非线程特有的promise中直接实现及早求值的线程特有的future。在这种情况下,future将只读视图返回给客户端,以便仅让新创建的线程能够解决这个future。


要在非线程特有的promise中实现隐式延迟线程特有的promise(比如由Alice ML提供),需要一种机制来确定何时首先需要future的值(例如,Oz中的WaitNeeded构造[14] )。 如果所有值都是对象,那么实现透明转发对象的能力就足够了,因为发送给转发器的第一条消息表明需要future的值。

假定系统支持消息传递,通过让resolve线程向future自己的线程发送消息,可以在线程特有的future中实现非线程特有的future。但是,这可以被视为不必要的复杂性。在基于线程的编程语言中,最具表现力的方法似乎是提供非线程特有的future,只读视图以及WaitNeeded构造或支持透明转发的混合。

求值策略

future的求值策略(可称为传future调用)是非确定性的:future的值将在创建future和使用其值之间的某个时间进行求值,但确切的时间不确定的,一次运行和另一次运行的求值时间会不一样。计算可以在创建future时开始(及早求值),或者仅在实际需要值时开始(懒惰求值),并且可以在中途暂停,或在一次运行中执行。一旦future被赋值,它就不会在访问future的时候重新计算;这就像传需求调用时使用的记忆化。

懒惰future是确定性具有惰性求值评估语义的future:future值的计算在首次需要时开始,与传需要调用一样。懒惰future在求值策略默认不是懒惰求值的语言中使用。例如,在C++11中,可以通过将std::launch::deferred启动策略传递给std::async以及计算值的函数来创建这种惰性future。

演员模型中的future语义

在演员模型中,形式为future <Expression>的表达式由它对Eval消息(环境为E,客户为C)的响应方式定义:future表达式通过向客户C发送新创建的演员来响应Eval消息F (计算<Expression>的响应的代理)作为返回值,与此同时<Expression>发送环境E和客户CEval消息。F的默认行为如下:

  • F收到请求R时,它会通过评估<Expression>继续检查它是否已收到响应(可以是返回值或抛出异常),如下所示:
    1. 如果它已经有响应V,那么
      • 如果V是返回值,则发送请求R.
      • 如果V是一个异常,那么就会把这个异常抛给请求R的客户。
    2. 如果它还没有响应,则R存储在F内的请求队列中。
  • F从评估<Expression>接到响应V时,则V存储在F
    • 如果V是返回值,则将所有排队的请求发送到V.
    • 如果V是一个异常,那么就会把这个异常抛出给每个排队请求的客户。

但是,一些future可以通过特殊方式处理请求以提供更大的并行性。例如,表达式1 + future factorial(n)可以创建一个新的future,其行为类似于数字1+factorial(n) 。这个技巧并不总是有效。例如,以下条件表达式:

if m>future factorial(n) then print("bigger") else print("smaller")

会挂起,直到factorial(n)这个future已回应询问m是否大于其自身的请求。

历史

futurepromise构造首先在诸如MultiLisp和Act 1之类的编程语言中实现。在并发逻辑编程语言中使用逻辑变量进行通信非常类似于future。这些开始于Prolog with FreezeIC Prolog,并成为真正的并发原语,包括关系语言、Concurrent Prolog、守卫霍恩子句(GHC)、Parlog、Strand、Vulcan、Janus、Oz-Mozart、Flow Java和Alice ML。来自数据流编程语言的单一赋值I-var ,源自Id并包含在Reppy的Concurrent ML中,非常类似于并发逻辑变量。

promise流水线技术(使用future来克服延迟)是Barbara Liskov和Liuba Shrira于1988年发明的[7],由Mark S. Miller、Dean Tribble和Rob Jellinghaus在大约1989年的Xanadu项目中独立发明。[15]

promise一词是由Liskov和Shrira创造的,尽管他们通过名称call-stream引用了流水线机制,现在很少使用它。

Liskov和Shrira的论文中描述的设计以及Xanadu中的promise流水线的实现都有一个限制,即promise值不是一等的:一个参数,或者一个call或send返回的值不能直接成为一个promise (所以前面给出的promise流水线的例子,它使用一个发送的结果作为另一个发送的参数的承诺,在call-stream设计或Xanadu实现中不能直接表达)。似乎promise和call-stream从未在Argus的任何公开发布中实现, [16] Liskov和Shrira论文中使用的编程语言。Argus的开发在1988年左右停止了。[17] Xanadu实现的promise流水线仅在1999年Udanax Gold[18]的源代码发布时才公开发布,并且在任何已发布的文档中都没有解释过。[19] Joule和E的后续实现支持完全一等的promise和resolver。

一些早期的演员语言,包括Act系列,[20][21] 支持并行消息传递和流水线消息处理,但不支持promise流水线。(虽然技术上可以在前两个中实现最后一个功能,但没有证据表明Act语言这样做了。)

2000年之后,由于消息传递的请求-响应模型,future和promise在用户界面响应和web开发中的应用重新引起了人们的兴趣。现在有几种主流语言对future和promise都有语言支持,最着名的是Java 5中的FutureTask(2004年公布)[22]以及.NET 4.5中的asyncawait结构(2010年发布,2012年发布)[23][24]很大程度上受到F#的异步工作流程(可追溯到2007年[25])的启发[26]。 随后被其他语言采用,特别是Dart(2014)[27],Python(2015)[28], Hack(HHVM)以及ECMAScript 7(JavaScript)、Scala和C++的草案。

实现列表

支持future、promise、并发逻辑变量、数据流变量或I-vars的语言,无论是通过直接语言支持还是在标准库中,包括:

还支持promise流水线的语言包括:

  • E
  • Joule

基于非标准库的future实现:

协程

future可以用协程[28]或生成器实现,[94] 从而产生相同的评估策略(例如,协同多任务或延迟评估)。

通道

future可以很容易地用通道实现:future是一个单元素的通道,而promise是一个发送到通道,实现future的过程。 [95] [96] 这允许future在支持通道(如CSP和Go)的并发编程语言中实现。由此产生的future是显式的,因为它们必须通过从通道读取而不是仅仅通过评估来获取。

参见

参考资料

  1. Friedman, Daniel; David Wise. . International Conference on Parallel Processing: 263–272. 1976.
  2. Hibbard, Peter. . New Directions in Algorithmic Languages, (ed.) Stephen A. Schuman, IRIA, 1976. 1976.
  3. Henry Baker; Carl Hewitt. . Proceedings of the Symposium on Artificial Intelligence Programming Languages,. ACM Sigplan Notices 12, 8: 55–59. August 1977 [2018-12-31]. (原始内容存档于2008-07-04).
  4. "SIP-14 – Futures and Promises 页面存档备份,存于"
  5. . www.erights.org. [2018-12-31]. (原始内容存档于2018-10-22).
  6. . [2018-12-31]. (原始内容存档于2005-09-25).
  7. Barbara Liskov; Liuba Shrira. . . ACM: 260–267. 1988. ISBN 0-89791-269-1. doi:10.1145/53990.54016. Also published in ACM SIGPLAN Notices 23(7).
  8. , Site Pen, 2010-05-03 [2018-12-31], (原始内容存档于2018-12-31)
  9. , , DE: Uni-SB, [2018-12-31], (原始内容存档于2008-10-08)
  10. , , DE: Uni-SB, [2018-12-31], (原始内容存档于2008-10-06)
  11. , E rights, [2018-12-31], (原始内容存档于2018-12-31)
  12. 500 lines or less, "A Web Crawler With asyncio Coroutines" by A. Jesse Jiryu Davis and Guido van Rossum 页面存档备份,存于 says "implementation uses an asyncio.Event in place of the Future shown here. The difference is an Event can be reset, whereas a Future cannot transition from resolved back to pending."
  13. , Haskell, [2018-12-31], (原始内容存档于2009-04-18)
  14. , Mozart Oz, [2018-12-31], (原始内容存档于2013-05-17)
  15. , Sunless Sea, [2018-12-31], (原始内容存档于2007-10-23)
  16. , MIT, [2018-12-31], (原始内容存档于2018-04-27)
  17. Liskov, Barbara, , Oral history, IEEE GHN, [2018-12-31], (原始内容存档于2014-11-22)
  18. , Udanax, [2018-12-31], (原始内容存档于2008-10-11)
  19. , E rights, [2018-12-31], (原始内容存档于2018-10-22)
  20. Henry Lieberman. . MIT AI memo 625. June 1981.
  21. Henry Lieberman. . MIT AI memo 626. June 1981.
  22. Goetz, Brian. . 2004-11-23. (原始内容存档于2018-11-13).
  23. . Blogs.msdn.com. [2014-05-13]. (原始内容存档于2012-04-07).
  24. . Msdn.microsoft.com. [2014-05-13]. (原始内容存档于2014-05-27).
  25. Don Syme; Tomas Petricek; Dmitry Lomov. . 2010-10-21 [2018-12-31]. (原始内容存档于2015-05-15).
  26. Tomas Petricek. . 2010-10-29 [2018-12-31]. (原始内容存档于2018-12-31).
  27. Gilad Bracha. . October 2014 [2018-12-31]. (原始内容存档于2016-07-02).
  28. . [2018-12-31]. (原始内容存档于2019-01-05).
  29. Kenjiro Taura; Satoshi Matsuoka; Akinori Yonezawa. . . American Mathematical Society: 275–292. 1994. 已忽略未知参数|citeseerx= (帮助)
  30. . [2018-12-31]. (原始内容存档于2016-03-04).
  31. . [2018-12-31]. (原始内容存档于2018-12-31).
  32. Steve Dekorte. . 2005 [2018-12-31]. (原始内容存档于2019-01-04).
  33. Rich Hickey. . 2009 [2018-12-31]. (原始内容存档于2016-02-18).
  34. Seif Haridi; Nils Franzen. . Mozart Global User Library. [2011-04-12]. (原始内容存档于2011-05-14).
  35. . docs.perl6.org. [2019-01-04]. (原始内容存档于2018-12-31).
  36. . Python.org. [2018-12-31]. (原始内容存档于2018-12-31).
  37. . Python.org. [2018-12-31]. (原始内容存档于2019-01-05).
  38. . PLT. [2012-03-02]. (原始内容存档于2012-03-23).
  39. . orthecreedence.github.io. [2018-12-31]. (原始内容存档于2017-11-10).
  40. . common-lisp.net. [2019-01-04]. (原始内容存档于2018-12-31).
  41. - Common Lisp的并行编程库
  42. . marijnhaverbeke.nl. [2018-12-31]. (原始内容存档于2020-08-06).
  43. . [2013-06-26]. (原始内容存档于2019-10-16).
  44. . [2013-06-26]. (原始内容存档于2019-10-16).
  45. . Qt Project. [2013-06-26]. (原始内容存档于2013-06-01).
  46. . Seastar project. [2016-08-22]. (原始内容存档于2016-08-20).
  47. . [2018-12-31]. (原始内容存档于2019-03-22).
  48. (PDF). [2018-12-31]. (原始内容存档 (PDF)于2020-05-08).
  49. . [2018-12-31]. (原始内容存档于2013-01-12).
  50. . cujojs.com. [2019-01-04]. (原始内容存档于2012-03-17).
  51. . 2018-12-25 [2018-12-31]. (原始内容存档于2019-01-16) GitHub.
  52. . promisesaplus.com. [2019-01-04]. (原始内容存档于2018-12-29).
  53. . dojotoolkit.org. [2019-01-04]. (原始内容存档于2018-12-31).
  54. . MochiKit.Async. [2019-01-04]. (原始内容存档于2018-12-31).
  55. . angularjs.org. [2019-01-04]. (原始内容存档于2015-06-23).
  56. . 2018-10-22 [2018-12-31]. (原始内容存档于2019-05-27) GitHub.
  57. . documentup.com. [2018-12-31]. (原始内容存档于2018-12-31).
  58. . 2019-01-03 [2018-12-31]. (原始内容存档于2019-01-17) GitHub.
  59. . yuilibrary.com. [2019-01-04]. (原始内容存档于2019-01-12).
  60. . yuilibrary.com. [2019-01-04]. (原始内容存档于2018-12-31).
  61. . 2019-01-04 [2018-12-31]. (原始内容存档于2018-12-28) GitHub.
  62. . JDeferred. [2019-01-04]. (原始内容存档于2019-01-08).
  63. . 2018-12-29 [2018-12-31]. (原始内容存档于2018-06-10) GitHub.
  64. . 2019-01-01 [2018-12-31]. (原始内容存档于2019-02-13) GitHub.
  65. . www.mikeash.com. [2019-01-04]. (原始内容存档于2018-12-31).
  66. . 2018-12-26 [2018-12-31]. (原始内容存档于2018-06-11) GitHub.
  67. . 2018-12-07 [2018-12-31]. (原始内容存档于2018-06-11) GitHub.
  68. . 2019-01-03 [2019-01-04]. (原始内容存档于2019-01-08) GitHub.
  69. . 2018-09-11 [2018-12-31]. (原始内容存档于2018-06-11) GitHub.
  70. . 2017-03-29 [2018-12-31]. (原始内容存档于2013-10-27) GitHub.
  71. . caml.inria.fr. [2018-12-31]. (原始内容存档于2015-07-06).
  72. . metacpan.org. [2019-01-04]. (原始内容存档于2019-01-01).
  73. . metacpan.org. [2018-12-31]. (原始内容存档于2018-12-31).
  74. . metacpan.org. [2019-01-04]. (原始内容存档于2019-01-04).
  75. . 2019-01-04 [2018-12-31]. (原始内容存档于2019-04-07) GitHub.
  76. . docs.python.org. [2018-12-31]. (原始内容存档于2018-12-31).
  77. . code.google.com. [2019-01-04]. (原始内容存档于2020-08-06).
  78. . twistedmatrix.com. [2019-01-04]. (原始内容存档于2018-12-31).
  79. Bengtsson, Henrik. . 2018-10-17 [2018-12-31]. (原始内容存档于2019-10-16) R-Packages.
  80. Bengtsson, Henrik. . 2018-10-17 [2018-12-31]. (原始内容存档于2019-10-16) R-Packages.
  81. . rubygems.org. [2019-01-04]. (原始内容存档于2018-12-31).
  82. . 2018-10-16 [2018-12-31]. (原始内容存档于2018-06-11) GitHub.
  83. . celluloid.io. [2019-01-04]. (原始内容存档于2018-12-31).
  84. . 2013-12-27 [2018-12-31]. (原始内容存档于2018-06-11) GitHub.
  85. . 2019-01-04 [2019-01-04]. (原始内容存档于2019-02-26) GitHub.
  86. . twitter.github.io. [2018-12-31]. (原始内容存档于2018-12-23).
  87. . bitbucket.org. [2018-12-31]. (原始内容存档于2018-12-31).
  88. . 2018-12-31 [2018-12-31]. (原始内容存档于2018-08-10) GitHub.
  89. . developer.apple.com. [2019-01-04]. (原始内容存档于2018-12-31).
  90. . 2018-10-15 [2018-12-31]. (原始内容存档于2018-06-12) GitHub.
  91. . 2019-01-02 [2019-01-04]. (原始内容存档于2018-06-10) GitHub.
  92. . 2019-01-03 [2018-12-31]. (原始内容存档于2018-06-11) GitHub.
  93. . SourceForge. [2018-12-31]. (原始内容存档于2018-12-31).
  94. . esdiscuss.org. [2018-12-31]. (原始内容存档于2018-11-13).
  95. . www.golangpatterns.info. [2019-01-04]. (原始内容存档于2019-01-04).
  96. . www.golangpatterns.info. [2019-01-04]. (原始内容存档于2019-01-04).

外部链接

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.