Future与promise

异步处理

计算机科学中,futurepromisedelaydeferred,是在某些并发编程语言中,指称用于同步程序执行英语Execution (computing)的一种构造。由于某些计算尚未结束,故而需要一个对象来代理这个未知的结果。这种构造起源于函数式编程和相关范型逻辑编程,其目的是将值与其运算过程解耦,从而允许更灵活地进行计算,特别是通过将其并行化来进行。后来它在分布式计算中得到了应用,用来减少网络通信往返的延迟。再后来async/await语法机制使其变得更有用,籍此允许以直接风格编写异步程序,而不再采用续体传递风格

术语

编辑

在1976年Daniel P. Friedman和David Wise提出了术语“promise”[1],同年Peter Hibbard称之为“eventual”[2]。1977年Henry Baker英语Henry Baker (computer scientist)Carl Hewitt英语Carl Hewitt在一篇论文中介绍了一个类似的概念“future”[3]

术语“future”、“promise”、“delay”和“deferred”通常用来称谓同样一种机制,在特定实现中可能只选用其中之一。在这种情况下,值与其运算过程是一起创建并且相互关联的:future若指称推迟设置的一个值[4],设置它的函数就可称谓为promise;promise若指称推迟设置的一个值[5],设置它的函数就可称谓为resolver;promise若指称一个异步函数[1],它的结果值所设置的就可称谓为future。设置一个推迟值也称为“决定/解决”(resolve)、“履行/实现”(fulfil)或“绑定”(bind)它。

推迟值及其设置者也存在区分称谓而同时使用的情况,二者的这种用法在不同实现中的差异将在专门章节中讨论。具体来说,future指称一个“只读”的变量的占位符视图,而promise指称一个可写的单赋值容器,使用其“成功”方法来设置这个future的值[6]。尤其是在定义future之时,无须指定设置其值的promise;并且可能有不同的promise,设置同一个future的值;但是对于一个给定future,仅可以进行一次设置。

历史

编辑

future及/或promise构造,首先实现于编程语言例如MultiLisp英语MultiLisp[4]Act 1之中。在并发逻辑编程英语Concurrent logic programming语言中,使用非常类似于future的逻辑变量进行通信[7]。这种语言开始于1982年的“Prolog with Freeze”和“IC Prolog”,并且在后来的语言中变成了真正的并发原语,比如Concurrent PrologParlog英语Parlog守卫霍恩子句(GHC)、Strand英语Strand (programming language)Janus英语Janus (concurrent constraint programming language)Oz(Mozart)和Alice ML英语Alice (programming language)[8][9]。叫做“I-变量”的单赋值变量,非常类似于并发逻辑变量,它起源于数据流程编程语言Id英语Id (programming language)的“I-结构”元件[10],并且包含在Reppy的Concurrent ML[11]

在1988年Barbara Liskov和Liuba Shrira,发明了promise流水线技术来克服传输延迟[5];Liskov和Shrira在论文中使用了术语“promise”,但是他们采用了现在少见的名称“call-stream”来提及流水线机制。在大约1989年Mark S. Miller英语Mark S. Miller、Dean Tribble和Rob Jellinghaus,于Xanadu项目中也独立发明了此技术[12]

Liskov和Shrira的论文中描述的设计,以及Xanadu中的promise流水线的实现,都有一个限制,即promise值不是头等的:call或send的参数或返回值,不能直接是promise。在Liskov和Shrira论文中使用的编程语言Argus,直至大约1988年停止开发,似乎都未曾在任何公开发布中实现promise和call-stream[13][14]。Xanadu实现的promise流水线,仅在1999年Udanax Gold的源代码发布时才公开发布[15],并且在任何已发布的文档中都没有解释过[16]

一些早期的演员语言,包括Act系列[17][18],支持并行消息传递和流水线式消息处理,但不支持promise流水线。Joule英语Joule (programming language)E语言的后续实现,支持完全头等的promise和resolver,还有promise流水线。

2000年之后,由于消息模式英语Messaging pattern请求-响应模型,在用户界面响应力英语ResponsivenessWeb开发中的应用,future和promise重新引起了人们的兴趣。现在一些主流语言对future和promise有了语言支持,最著名的是2004年发行的Java 5中的FutureTask[19],以及2012年发行的.NET框架 4.5中的async/await结构[20][21],它在很大程度上受到可追溯到2007年的“F#异步编程模型”的启发[22][23]async/await随后被其他语言采用,特别是2014年的Dart 1.9[24]、2014年发行的HackHHVM)、2015年的Python 3.5[25]ECMAScript 2017、2019年的Rust 1.39和C++20等。

实现列表

编辑

编程语言支持

编辑

下面列出支持future、promise和并发逻辑变量、数据流程变量或I-变量的语言,包括直接在语言中支持还有在标准库中支持:

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

非标准库的实现

编辑

自行实现

编辑

future可以用协程生成器实现[25],从而具有相同的求值策略(例如协同多任务或延迟求值)。

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

编程语言典型示例

编辑

Python

编辑

Pythonconcurrent.futures模块,提供了异步英语Asynchrony (computer programming)执行英语Execution (computing)可调用对象的高层接口。异步执行可以使用线程池执行器ThreadPoolExecutor,通过多个线程来进行;或使用进程英语Pool (computer science)执行器ProcessPoolExecutor,通过分立的多个进程来进行。concurrent.futures.Future封装了可调用对象的异步执行,其实例由执行器抽象类Executor的这两个子类的submit()方法创建[41]

在下面的例子中,定义了load_url(),用来检索一个URL所指定的单一网页并报告其内容。使用with语句指定采用线程池执行器,这确保了线程由它及时清理。用这个执行器的submit()方法启动所有的装载操作任务,并使用字典推导式为其创建的每个future标记上对应的URL。采用模块函数as_completed(),在指定的Future类的诸实例“已齐全”(completed)之时,建立在其上的迭代器

import concurrent.futures
import urllib.request

URLS = [
    'https://www.python.org/',
    'https://pypi.org/search/',
    'https://no-such-url'
    ]

def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

with concurrent.futures.ThreadPoolExecutor() as executor:
    future_to_url = {executor.submit(load_url, url, 60) : url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print(f'{url!r} generated an exception: {exc!s}')
        else:
            print(f'{url!r} page is {len(data):d} bytes')

CPython中,由于全局解释器锁(GIL),保证一时只有一个线程可以执行Python字节码;一些扩展模块被设计为在进行计算密集任务时释放GIL,还有在进行I/O时总是释放GIL。线程池执行器的max_workers参数的缺省值是min(32, os.cpu_count() + 4),这个缺省值为I/O踊跃英语I/O bound任务保留至少5个worker线程,为释放GIL的CPU踊跃任务利用最多32个CPU逻辑核心。想要更好利用多核心机器的计算资源,可以使用进程池执行器或多进程模块multiprocessing。对于同时运行多个I/O踊跃英语I/O bound任务,仍然适合采用线程池执行器或线程模块threading

Python的异步I/O模块asyncio,是采用async/await语法编写并发代码的库[104]asyncio模块基于低层事件循环,提供的高层API包括了:并发运行Python协程并拥有在其执行上的完全控制,进行网络IOIPC,控制子进程,通过队列分布任务英语Task (computing)同步并发代码。asyncio模块的同步原语,在设计上类似于threading模块的同步原语,但与之相比有两个重要差异:asyncio模块的同步原语不是线程安全的,故而不能用于OS线程同步;这些同步原语的方法不接受超时英语Timeout (computing)实际参数。

asyncio模块提供的低层API中有asyncio.Future对象,用来桥接低层基于回调的代码和高层采用async/await语法的代码,它在设计上模仿了concurrent.futures.Future对象。这两个模块的Future对象的set_result()方法标记这个Future对象为“已完毕”(done)并设置它的结果,而set_exception()方法标记这个Future对象为已完毕并设置一个例外;done()方法在这个Future对象已完毕时返回Trueresult()方法返回这个Future对象所设置的结果,或者引发其所设置的例外

二者的关键差异包括了:asyncio.Future是可期待(awaitable)对象,而concurrent.futures.Future对象不可以被期待(awaited)。asyncio.Future.result()不接受超时实际参数,如果此刻这个Future对象的结果仍不可获得,它引发一个InvalidStateError例外;而concurrent.futures.Future.result(),可接受超时英语Timeout (computing)timeout)实际参数,如果此刻结果仍未“完全”(completed),它等待指定时间后若仍未完全则引发TimeoutError例外,如果超时未指定或指定为None,则对等待时间没有限制。

JavaScript

编辑

JavaScript中,Promise对象表示一个异步英语Asynchrony (computer programming)运算的最终完成或失败,及其结果值或失败理由。Promise对象是对一个值的代理(proxy),这个值在创建这个promise之时不必需已知。promise允许为异步行动的最终成功值或失败理由关联上处理器,这使得异步方法像同步方法那样返回值:并非立即返回最终值,异步方法返回一个promise来在将来的某一点上提供这个值。

在JavaScript生态系统中,Promise对象在成为语言本身的一部份之前很久就有了多种实现[105]。尽管各有不同的内部表示,至少几乎所有类似Promise的对象都实现了“可接续”(thenable)接口[59]。可接续对象实现了.then()方法,Promise对象就是可接续的。

一个promise被称为已落实(settled),如果它要么已履行(fulfilled)要么已拒绝(rejected),而不再待定(pending)。Promise实例的.then()方法,接受一到二个实际参数,它们是作为处理器异步执行的回调函数,分别针对了这个promise的已履行和已拒绝情况;此方法返回一个新的promise,它决定(resolve)出其所调用处理器的返回值,或者在原来这个promise未被处理的情况下,仍决定出其已落实的值。Promise实例的.catch()方法,实际上就是置空针对已履行情况的回调函数,而只有针对已拒绝情况的回调函数的.then()方法。

Promise()构造子创建Promise对象,它主要用于包装仍基于回调的API所提供的异步英语Asynchrony (computer programming)运算,要注意它只能通过new算子来构造:new Promise(executor)Promise()构造子的实际参数叫做“执行器”(executor),它是由这个构造子同步执行的一个函数,它有可自行指定名字的两个函数形式参数,比如指定为resolveFuncrejectFunc,这两个函数接受任何类型的单一实际参数。

Promise()构造子返回的Promise对象,在要么resolveFunc函数要么rejectFunc函数被调用之时,它就成为“已决定”(resolved)的。要注意如果在调用这两个函数之一的时候,将另一个Promise对象作为实际参数传递给它,则这个Promise对象可以称为已决定但仍未落实。在执行器中有任何错误抛出,都会导致这个promise成为已拒绝状态,而返回值会被忽略。

Promise类提供四个静态方法来实施异步任务英语Task (computing)并发,其中的Promise.allSettled()方法,接受有多个promise的可迭代对象作为输入,当所有输入的promise都已落实之时,返回一个单一的已履行的Promise对象,它决定出描述每个输入promise的结果的一个对象数组;其中每个对象都有status属性,它是字符串"fulfilled"或者"rejected";还有在status"fulfilled"时出现的属性value,或者在status"rejected"时出现的属性reason

在下面例子中,全局fetch()方法,启动从网络上取回一个资源的过程,并返回一旦响应可获得就履行的一个promise;这个promise决定出表示对这个请求响应的一个Response对象,它只在遇到网络错误之时拒绝。Response.text()方法返回一个promise,它决定出这个响应的主体的一个文本表示。

const urls = [
  'https://developer.mozilla.org/',
  'https://javascript.info/promise-api',
  'https://no-such-url'
];

const fetchPromises = urls.map((url) => fetch(url));

Promise.allSettled(fetchPromises)
  .then((results) => { 
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        if (result.value.ok) { 
          result.value.text()
            .then((data) => {
              console.log(`${urls[num]}: ${data.length} bytes`);
            })
            .catch((err) => {
              console.error(err);
            });
        } 
        else {
          console.log(`${urls[num]}: ${result.value.status}`);
        }
      }
      else if (result.status == "rejected") {
        console.error(`${urls[num]}: ${result.reason}`);
      }
    });
  });

JavaScript是天然的单线程的,所以在给定时刻只有一个任务英语Task (computing)执行英语Execution (computing),但控制可以在不同的promise之间转移,使得多个promise表现为并发执行。在JavaScript中并行执行只能通过分立的worker后台线程来完成[106]

同基于promise的代码合作的一种更简单的方式,是使用async/await关键字。在函数开始处增加async,使其成为异步函数,异步英语Asynchrony (computer programming)函数总是返回一个promise。在异步函数中,可以在对返回一个promise的函数的调用之前,使用await关键字;这使得代码在这一点上等待直到这个promise已落实下来,然后在这一点上promise的已履行的值被当作返回值,而已拒绝的理由被作为例外抛出。可以使用try...catch块进行例外处理,就如同这个代码是同步的一样。

只读视图

编辑

在某些编程语言(如OzEAmbientTalk英语AmbientTalk)中,可以获得推迟值的“只读视图”,允许在解决(resolve)出这个值之后通过它来读取,但不允许通过它来解决这个值:

  • 在Oz语言中,!!运算符用于获得只读视图。
  • 在E语言和AmbientTalk中,推迟值由一对称为“promise/resolver对”的值表示。promise表示只读视图,需要resolver来设置推迟值。
  • 在C++11中,std::future提供了一个只读视图。该值通过使用std::promise直接设置,或使用std::packaged_taskstd::async设置为函数调用的结果。
  • 在Dojo Toolkit的1.5版本的Deferred API中,“仅限consumer的promise对象”表示只读视图。[107]
  • Alice ML英语Alice (programming language)中,future提供“只读视图”[27],而promise包含future和解决future的能力[108]
  • 在.NET 4.0中,System.Threading.Tasks.Task<T>表示只读视图。解决值可以通过System.Threading.Tasks.TaskCompletionSource<T>来完成。

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

有关结构

编辑

“future”是事件同步原语的特例,它只能完成一次。通常,事件可以重置为初始的空状态,因此可以根据需要多次完成。[109]

“I-var”是具有下面定义的阻塞语义的future。它起源于Id语言中包含I-var的“I-structure”数据结构。可以使用不同值多次设置的有关同步构造称为“M-var”。M-var支持take(采取)或put(放置)当前值的原子性操作,这里采取这个值还将M-var设置回其初始的“空”状态。[110]

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

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

隐式与显式future

编辑

对future的使用可以是“隐式”的,任何对future的使用都会自动获得它的值,它就像是普通的引用一样;也可以是“显式”的,用户必须调用函数来获取值,例如Java中的Future[32]或CompletableFuture[33]get方法。获得一个显式的future的值可以称为“刺激”(stinging)或“强迫”(forcing)。显式future可以作为库来实现,而隐式future则通常作为语言的一部分来实现。

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

阻塞与非阻塞语义

编辑

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

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

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

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

promise流水线

编辑

分布式系统中使用推迟值可以显著地减少传输延迟。例如,指称推迟值的promise,成就了“promise流水线”[112][113],就像在E语言和Joule英语Joule (programming language)语言中实现的那样,它在Argus语言中称为“call-stream”[5]

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

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

可以展开为

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

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

上面的表达式可以使用E语言的语法写为:

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

其中x <- a()表示将消息a()异步发送给x。它可以展开为:

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

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

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

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

有特定线程的future

编辑

某些语言比如Alice ML英语Alice (programming language),定义的future可以关联着计算这个future值的特定线程[27]。这种计算可以通过spawn exp,在创建future时及早地开始;或者通过lazy exp,在首次需要其值时懒惰地开始。在延迟计算的意义上,懒惰的future类似于thunk 。

Alice ML还支持可由任何线程解决的future,并称谓它们为“promised future”[108]。这里的promise是给future的显式把柄(handle),它通过多态库函数Promise.promise来创建,所有promise都关联的一个future,创建一个新promise也就创建了一个新鲜的future;这个future通过Promise.future来提取,并且通过显式的应用Promise.fulfill于对应的promise来消除,即在全局中将这个future替代为一个值。Alice ML不支持promise流水线,转而对于future,包括关联着promise的future,流水线是自然而然地发生的。

在没有特定线程的future(如Alice ML所提供的)中,通过在创建这个future的同时创建一个计算这个值的线程,可以直接实现及早求值的有特定线程的future。在这种情况下,最好将只读视图返回给客户,以便仅让新创建的线程能够解决这个future。

要在没有特定线程的future中,实现隐式惰性的有特定线程的future,需要一种机制来确定何时首次需要future的值(例如,Oz中的WaitNeeded构造[114] )。 如果所有值都是对象,那么有实现透明转发对象的能力就足够了,因为发送给转发器的首条消息表明需要future的值。

假定系统支持消息传递,在有特定线程的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>的表达式,以它对具有环境E和客户C的Eval消息的响应方式来定义:future表达式通过向客户C发送新创建的演员F(计算<Expression>的响应的代理)作为返回值来响应Eval消息,与之并发的向<Expression>发送具有环境E和客户F的Eval消息。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是否大于其自身的请求。

参见

编辑

引用

编辑
  1. ^ 1.0 1.1 Daniel Friedman; David Wise. The Impact of Applicative Programming on Multiprocessing. International Conference on Parallel Processing: 263–272. 1976. We have proposed a new semantics for cons and its extractor functions first and rest which avoids the construction of those portions of structures that are never accessed. ……
    Using the function cons as a paradigm of structure-creating functions, we briefly explain its semantics. When cons is invoked by the user, the value returned is a pointer to a newly built structure. Rather than evaluate the arguments to cons and create the complete structure, we create a structure consisting of two suspensions. A suspension consists of a reference to the form whose evaluation was deferred and a reference to the environment of variable bindings in which the suspension was originally created. ……
    When either of the functions first or rest is invoked, the following events occur. A designated field of the argument is checked to determine if it contains a suspension (suspensions are flagged and easily distinguished); if not, then its contents is returned. If a suspension is present, then the evaluator is invoked upon the designated form within the preserved environment. The result is stored back in the designated field in place of the suspension (for next time); and the value is returned as a final result. ……
    As a result of suspending, evaluations are delayed as long as possible. ……
    A fortunate side-effect of suspending the creation of data structures is the ability to deal with infinite structures. Consider the list defined (but never completely constructed) by the invocation of <terms[O]> where
      <terms[n]> ≡ <cons[<recip[<square[n]>]>
                         <terms[<addl[n]>]> ]> .
    That list, the reciprocals of the squares of all the positive integers, might be familiar since its sum, excluding the first term, converges to π2/6. Suppose that z were bound to the result of <terms[O]>; in fact, because of the suspending cons, z is initially bound only to a "promise" of this result.
     
  2. ^ Hibbard, Peter. Parallel Processing Facilities. New Directions in Algorithmic Languages, (ed.) Stephen A. Schuman, IRIA, 1976. 1976. 
  3. ^ Henry Baker; Carl Hewitt. The Incremental Garbage Collection of Processes. Proceedings of the Symposium on Artificial Intelligence Programming Languages,. ACM Sigplan Notices 12, 8: 55–59. August 1977 [2024-01-26]. (原始内容存档于2024-04-12). In this paper we consider an "eager beaver" evaluator for an applicative programming language which starts evaluating every subexpression as soon as possible, and in parallel. This is done through the mechanism of futures, which are roughly Algol-60 "thunks" which have their own evaluator process ("thinks"?). (Friedman and Wise [10] call futures "promises", while Hibbard [13] calls them "eventuals".) When an expression is given to the evaluator by the user, a future for that expression is returned which is a promise to deliver the value of that expression at some later time, if the expression has a value. A process is created for each new future which immediately starts to work evaluating the given expression. ……
    The intuitive semantics associated with a future is that it runs asynchronously with its parent's evaluation. This effect can be achieved by either assigning a different processor to each future, or by multiplexing all of the futures on a few processors. Given one such implementation, the language can easily be extended with a construct having the following form: "(EITHER <e1> <e2> … <en>)" means evaluate the expressions <ei> in parallel and return the value of "the first one that finishes".
     
  4. ^ 4.0 4.1 4.2 Halstead, Robert H. Jr. MultiLisp: A Language for Concurrent Symbolic Computation. ACM Transactions on Programming Languages and Systems. October 1985, 7 (4): 501–538. S2CID 1285424. doi:10.1145/4472.4478 . Multilisp's principal construct for both creating tasks and synchronizing among them is the future. The construct (future X) immediately returns a future for the value of the expression X and concurrently begins evaluating X. When the evaluation of X yields a value, that value replaces the future. The future is said to be initially undetermined; it becomes determined when its value has been computed. An operation (such as addition) that needs to know the value of an undetermined future will be suspended until the future becomes determined, but many operations, such as assignment and parameter passing, do not need to know anything about the values of their operands and may be performed quite comfortably on undetermined futures. ……
    The future construct in Multilisp, for example, offers a way to introduce parallelism that fits very nicely with the principal program operation of Multilisp — expression evaluation. Also, no special care is required to use a value generated by future. Synchronization between the producer and the users of a future’s value is implicit, freeing the programmer’s mind from a possible source of concern.
     
  5. ^ 5.0 5.1 5.2 Barbara Liskov; Liuba Shrira. Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems. Proceedings of the SIGPLAN '88 Conference on Programming Language Design and Implementation; Atlanta, Georgia, United States. ACM: 260–267. 1988 [2024-01-26]. ISBN 0-89791-269-1. doi:10.1145/53990.54016. (原始内容存档于2024-01-26). Promises were designed to support an efficient asynchronous remote procedure call mechanism for use by components of a distributed program. A promise is a place holder for a value that will exist in the future. It is created at the time a call is made. The call computes the value of the promise, running in parallel with the program that made the call. When it completes, its results are stored in the promise and can then be “claimed” by the caller. ……
    Call-streams allow a sender to make a sequence of calls to a receiver without waiting for replies. The stream guarantees that the calls will be delivered to the receiver in the order they were made and that the replies from the receiver will be delivered to the sender in call order. ……
    The design of promises was influenced by the future mechanism of MultiLisp[5]. Like futures, promises allow the result of a call to be picked up later. However, promises extend futures in several ways: Promises are strongly typed and thus avoid the need for runtime checking to distinguish them from ordinary values. They allow exceptions from the called procedure to be propagated in a convenient manner. Finally, they are integrated with the call-stream mechanism and address problems such as node failures and network partitions that do not arise in a single-machine environment. ……
    There are two reasons for using stream calls instead of RPCs: they allow the caller to run in parallel with the sending and processing of the call, and they reduce the cost of transmitting the call and reply messages. RPCs and their replies are sent over the network immediately, to minimize the delay for a call. Stream calls and their replies, however, are buffered and sent when convenient; in the case of sends, normal replies can be omitted. Buffering allows us to amortize the overhead of kernel calls and the transmission delays for messages over several calls, especially for small calls and replies.
      Also published in ACM SIGPLAN Notices 23(7).
  6. ^ Kisalaya Prasad, Avanti Patil, Heather Miller. Futures and Promises. [2024-03-11]. (原始内容存档于2024-03-03). 
  7. ^ Domenico Talia. Survey and comparison of PARLOG and Concurrent Prolog. 1990 [2024-03-13]. (原始内容存档于2024-03-13). Concurrent logic languages are born from a new interpretation of Horn clauses, the process interpretation. According to this interpretation, an atomic goal ← C can be viewed as a process, a conjunctive goal ← C1, …, Cn, as a process network, and a logic variable shared between two clauses can be viewed as a communication channel between two processes. 
  8. ^ Gert Smolka. Concurrent Constraint Programming Based on Functional Programming. 1998 [2024-03-13]. (原始内容存档于2024-03-13). As base language we choose a dynamically type language DML that is obtained from SML by eliminating type declarations and static type checking. ……
    A state is a finite function σ mapping addresses a to so-called units u. Units are either primitive values other than names or representations of records, variants, reference cells, functions, and primitive operations …… A match Match is a sequence of clauses (p1 ⇒ e1 | … | pk ⇒ ek). ……
    We now extend DML with logic variables, one of the essentials of logic programming. Logic variables are a means to represent in a state partial information about the values of addresses. Logic variables are modelled with a new unit lvar. The definition of states is extended so that a state may map an address also to lvar or an address. ……
    A match …… blocks until the store contains enough information to commit to one of the clauses or to know that none applies.
     
  9. ^ 9.0 9.1 Jan Schwinghammer. A Concurrent Lambda Calculus with Promises and Futures. 2002 [2024-03-14]. (原始内容存档于2024-03-14). General logic variables stem from the class of logic programming languages such as Prolog [Pro85, SS94, JL87]. Initially, when freshly introduced, they carry no value. Therefore they allow for the stepwise construction of values, using further logic variables for the construction of subvalues if necessary. They are transient, in that they are identified with their value as soon as this becomes available. This provides a mechanism for implicit synchronization of concurrent threads that share a logic variable: A thread reading the variable automatically suspends while sufficient information is not available.
    We will be concerned with futures and promises, which differ from general logic variables in that a distinction is made between reading and writing them. Bidirectional unification can be replaced by (single-) assignment.
     
  10. ^ 10.0 10.1 Rishiyur S. Nikhil. ID Reference Manual (PDF). 1991 [2024-03-13]. (原始内容存档 (PDF)于2024-05-19). A component of a data structure may have I-structure semantics (as opposed to functional or M-structure semantics). For such a component, no value is specified when the data structure is created; instead, a separate assignment statement is used. An I-structure component may be in one of two states: be full (with a value), or empty. all components begin in the empty state (when the data structure is allocated).
    An I-structure component has a single assignment restriction, i.e. it can only be assigned once, at which point its state goes from empty to full. Any attempt to assign it more than once is caught as a runtime error. The component can be read an arbitrary number of times.
    ……
    An M-structure component can be assigned with a put operation and read with a take operation. A value can be put only into an empty component — it is a runtime error if it is already full. Many take's may be attempted concurrently on a component.
     
  11. ^ 11.0 11.1 The Concurrent ML Reference Manual — The SyncVar structure. [2024-03-13]. (原始内容存档于2024-05-10). The SyncVar structure provides Id-style synchronous variables (or memory cells). These variables have two states: empty and full. An attempt to read a value from an empty variable blocks the calling thread until there is a value available. An attempt to put a value into a variable that is full results in the Put exception being raised. There are two kinds of synchronous variables: I-variables are write-once, while M-variables are mutable. 
  12. ^ Promise, Sunless Sea, [2018-12-31], (原始内容存档于2007-10-23) 
  13. ^ Argus, MIT, [2018-12-31], (原始内容存档于2018-04-27) 
  14. ^ Liskov, Barbara, Distributed computing and Argus, Oral history, IEEE GHN, [2018-12-31], (原始内容存档于2014-11-22) 
  15. ^ Gold, Udanax, [2018-12-31], (原始内容存档于2008-10-11) 
  16. ^ Pipeline, E rights, [2018-12-31], (原始内容存档于2018-10-22) 
  17. ^ Henry Lieberman. A Preview of Act 1. MIT AI memo 625. June 1981. 
  18. ^ Henry Lieberman. Thinking About Lots of Things at Once without Getting Confused: Parallelism in Act 1. MIT AI memo 626. June 1981. 
  19. ^ Future and FutureTask in java. [2024-03-11]. (原始内容存档于2024-03-11). 
  20. ^ 20.0 20.1 Async in 4.5: Worth the Await – .NET Blog. April 3rd, 2012 [2024-03-20]. (原始内容存档于2024-03-20). 
  21. ^ 21.0 21.1 21.2 Asynchronous Programming with Async and Await (C# and Visual Basic). Msdn.microsoft.com. [2014-05-13]. (原始内容存档于2014-05-27). 
  22. ^ Don Syme; Tomas Petricek; Dmitry Lomov. The F# Asynchronous Programming Model, PADL 2011. 2010-10-21. This paper describes the asynchronous support in F# 2.0. While the core idea was released and published in book form 2007, the model has not been described in the conference literature. 
  23. ^ Tomas Petricek. Asynchronous C# and F# (I.): Simultaneous introduction. 2010-10-29 [2018-12-31]. (原始内容存档于2018-12-31). Currently, you can run the operation on a background thread or using a Task, but coordinating multiple such operations is difficult. …… This problem has been the main motivation for including asynchronous workflows in F# about 3 years ago. In F#, this also enabled various interesting programming styles - for example creating GUI using asynchronous workflows ……. The C# asynchronous programming support and the await keyword is largely inspired by F# asynchronous workflows ……. 
  24. ^ 24.0 24.1 Gilad Bracha. Dart Language Asynchrony Support: Phase 1. October 2014 [2018-12-31]. (原始内容存档于2016-07-02). 
  25. ^ 25.0 25.1 PEP 0492 – Coroutines with async and await syntax. [2018-12-31]. (原始内容存档于2019-01-05). 
  26. ^ Kenjiro Taura; Satoshi Matsuoka; Akinori Yonezawa. ABCL/f: A Future-Based Polymorphic Typed Concurrent Object-Oriented Language – Its Design and Implementation.. In Proceedings of the DIMACS workshop on Specification of Parallel Algorithms, number 18 in Dimacs Series in Discrete Mathematics and Theoretical Computer Science. American Mathematical Society: 275–292. 1994. CiteSeerX 10.1.1.23.1161 . 
  27. ^ 27.0 27.1 27.2 Alice manual — Future. [2024-03-13]. (原始内容存档于2024-04-17). 
  28. ^ Dart SDK dart async Completer. [2018-12-31]. (原始内容存档于2016-03-04). 
  29. ^ Task. [2018-12-31]. (原始内容存档于2018-12-31). 
  30. ^ GHC User’s Guide — 6. Language extensions — 6.15. Parallel and Concurrent. [2024-03-13]. (原始内容存档于2024-03-13). 
  31. ^ Steve Dekorte. Io, The Programming Language. 2005 [2018-12-31]. (原始内容存档于2019-01-04). 
  32. ^ 32.0 32.1 java.util.concurrent.Future. [2024-03-11]. (原始内容存档于2024-05-21). 
  33. ^ 33.0 33.1 java.util.concurrent.CompletableFuture. [2024-03-11]. (原始内容存档于2024-04-29). 
  34. ^ Using promises. Mozilla Developer Network. [23 February 2021]. (原始内容存档于2024-05-24). 
  35. ^ Making asynchronous programming easier with async and await. Mozilla Developer Network. [23 February 2021]. (原始内容存档于2022-03-05). 
  36. ^ Rich Hickey. changes.txt at 1.1.x from richhickey's clojure. 2009 [2018-12-31]. (原始内容存档于2016-02-18). 
  37. ^ Parallelism with Futures. [2024-01-22]. (原始内容存档于2024-03-06). 
  38. ^ Task Parallel Library (TPL). [2024-03-11]. (原始内容存档于2024-05-21). 
  39. ^ Future – Kotlin Programming Language. [2024-03-12]. (原始内容存档于2024-03-12). 
  40. ^ Seif Haridi; Nils Franzen. Tutorial of Oz. Mozart Global User Library. [2011-04-12]. (原始内容存档于2011-05-14). 
  41. ^ 41.0 41.1 The Python Standard Library — Concurrent Execution — concurrent.futures — Launching parallel tasks. [2018-12-31]. (原始内容存档于2018-12-31). 
  42. ^ Python 3.2 发布. Python.org. [2018-12-31]. (原始内容存档于2018-12-31). 
  43. ^ PEP 3148 – futures - execute computations asynchronously. [2018-12-31]. (原始内容存档于2018-12-31). The proposed design of this module was heavily influenced by the Java java.util.concurrent package [1]. The conceptual basis of the module, as in Java, is the Future class, which represents the progress and result of an asynchronous computation. The Future class makes little commitment to the evaluation mode being used e.g. it can be used to represent lazy or eager evaluation, for evaluation using threads, processes or remote procedure call.
    Futures are created by concrete implementations of the Executor class (called ExecutorService in Java). The reference implementation provides classes that use either a process or a thread pool to eagerly evaluate computations.
     
  44. ^ Python 3.5.0 发布. Python.org. [2018-12-31]. (原始内容存档于2019-01-05). 
  45. ^ class Promise. raku.org. [2022-08-19]. (原始内容存档于2024-03-12). 
  46. ^ Future in std::future - Rust. doc.rust-lang.org. [2023-12-16]. (原始内容存档于2024-05-22). 
  47. ^ scala.concurrent包页面存档备份,存于互联网档案馆
  48. ^ Blackbird promise library - Blackbird. orthecreedence.github.io. [2018-12-31]. (原始内容存档于2017-11-10). 
  49. ^ Eager Future2. common-lisp.net. [2019-01-04]. (原始内容存档于2018-12-31). 
  50. ^ - Common Lisp的并行编程库[永久失效連結]
  51. ^ PCall. marijnhaverbeke.nl. [2018-12-31]. (原始内容存档于2020-08-06). 
  52. ^ Chapter 30. Thread 4.0.0. [2013-06-26]. (原始内容存档于2019-10-16). 
  53. ^ Dlib C++ Library #thread_pool. [2013-06-26]. (原始内容存档于2019-10-16). 
  54. ^ QtCore 5.0: QFuture Class. Qt Project. [2013-06-26]. (原始内容存档于2013-06-01). 
  55. ^ Seastar. Seastar project. [2016-08-22]. (原始内容存档于2016-08-20). 
  56. ^ GitHub – facebook/folly: An open-source C++ library developed and used at Facebook.. [2018-12-31]. (原始内容存档于2019-03-22). 
  57. ^ Threads Slides of POCO (PDF). [2018-12-31]. (原始内容存档 (PDF)于2020-05-08). 
  58. ^ Groovy GPars. [2018-12-31]. (原始内容存档于2013-01-12). 
  59. ^ 59.0 59.1 Promises/A+. promisesaplus.com. [2019-01-04]. (原始内容存档于2018-12-29). 
  60. ^ Promises/A+ Conformant Implementations. [2018-12-31]. (原始内容存档于2018-12-31). 
  61. ^ cujoJS: Javascript architectural toolkit. cujojs.com. [2019-01-04]. (原始内容存档于2012-03-17). 
  62. ^ A solid, fast Promises/A+ and when() implementation, plus other async goodies.: cujojs/when. 2018-12-25 [2018-12-31]. (原始内容存档于2019-01-16) –通过GitHub. 
  63. ^ Dojo Deferreds and Promises - Archived Tutorial - Dojo Toolkit. dojotoolkit.org. [2019-01-04]. (原始内容存档于2018-12-31). 
  64. ^ manage asynchronous tasks. MochiKit.Async. [2019-01-04]. (原始内容存档于2018-12-31). 
  65. ^ jQuery. [2018-12-31]. (原始内容存档于2012-02-29). 
  66. ^ Deferred Object. [2024-01-26]. (原始内容存档于2012-10-30). 
  67. ^ CommonJS Promises/A页面存档备份,存于互联网档案馆
  68. ^ AngularJS — Superheroic JavaScript MVW Framework. angularjs.org. [2019-01-04]. (原始内容存档于2015-06-23). 
  69. ^ kriskowal/q. [2024-01-24]. (原始内容存档于2024-05-09). 
  70. ^ A lightweight library that provides tools for organizing asynchronous code: tildeio/rsvp.js. 2019-01-03 [2018-12-31]. (原始内容存档于2019-01-17) –通过GitHub. 
  71. ^ :bird: :zap: Bluebird is a full featured promise library with unmatched performance.: petkaantonov/bluebird. 2019-01-04 [2018-12-31]. (原始内容存档于2018-12-28) –通过GitHub. 
  72. ^ promise页面存档备份,存于互联网档案馆
  73. ^ JDeferred. JDeferred. [2019-01-04]. (原始内容存档于2019-01-08). 
  74. ^ Asynchronous Java made easier. Contribute to linkedin/parseq development by creating an account on GitHub. 2018-12-29 [2018-12-31]. (原始内容存档于2018-06-10) –通过GitHub. 
  75. ^ Proxying futures library for Objective-C. Contribute to mikeash/MAFuture development by creating an account on GitHub. 2019-01-01 [2018-12-31]. (原始内容存档于2019-02-13) –通过GitHub. 
  76. ^ mikeash.com: Friday Q&A 2010-02-26: Futures. www.mikeash.com. [2019-01-04]. (原始内容存档于2018-12-31). 
  77. ^ An Objective-C Class which implements the Promises/A+ specification.: couchdeveloper/RXPromise. 2018-12-26 [2018-12-31]. (原始内容存档于2018-06-11) –通过GitHub. 
  78. ^ Futures, for Objective-C, that automatically collapse so it's nearly impossible to mix up the level of future nesting despite the lack of generics.: Strilanc/ObjC-CollapsingFutures. 2018-12-07 [2018-12-31]. (原始内容存档于2018-06-11) –通过GitHub. 
  79. ^ Promises for Swift & ObjC. Contribute to mxcl/PromiseKit development by creating an account on GitHub. 2019-01-03 [2019-01-04]. (原始内容存档于2019-01-08) –通过GitHub. 
  80. ^ Objective-C Promises in the CommonJS style. Contribute to mproberts/objc-promise development by creating an account on GitHub. 2018-09-11 [2018-12-31]. (原始内容存档于2018-06-11) –通过GitHub. 
  81. ^ OAPromise is an API separating async operations and their callbacks, adding consistency and useful features like fall-through errors and progress reports.: oleganza/OAPromise. 2017-03-29 [2018-12-31]. (原始内容存档于2013-10-27) –通过GitHub. 
  82. ^ Lazy. caml.inria.fr. [2018-12-31]. (原始内容存档于2015-07-06). 
  83. ^ Future - represent an operation awaiting completion - metacpan.org. metacpan.org. [2019-01-04]. (原始内容存档于2019-01-01). 
  84. ^ Promises - An implementation of Promises in Perl - metacpan.org. metacpan.org. [2018-12-31]. (原始内容存档于2018-12-31). 
  85. ^ Reflex - Class library for flexible, reactive programs. - metacpan.org. metacpan.org. [2019-01-04]. (原始内容存档于2019-01-04). 
  86. ^ Promises/A implementation for PHP. Contribute to reactphp/promise development by creating an account on GitHub. 2019-01-04 [2018-12-31]. (原始内容存档于2019-04-07) –通过GitHub. 
  87. ^ promise — Ultra-performant Promise implementation in Python. [2024-01-23]. (原始内容存档于2024-01-23). 
  88. ^ Deferred Reference. twistedmatrix.com. [2024-01-23]. (原始内容存档于2024-04-25). 
  89. ^ 89.0 89.1 Bengtsson, Henrik. future: Unified Parallel and Distributed Processing in R for Everyone. 2018-10-17 [2018-12-31]. (原始内容存档于2019-10-16) –通过R-Packages. 
  90. ^ promise - RubyGems.org - your community gem host. rubygems.org. [2019-01-04]. (原始内容存档于2018-12-31). 
  91. ^ Ruby bindings for libuv. Contribute to cotag/libuv development by creating an account on GitHub. 2018-10-16 [2018-12-31]. (原始内容存档于2018-06-11) –通过GitHub. 
  92. ^ Celluloid: Actor-based Concurrent Objects for Ruby. celluloid.io. [2019-01-04]. (原始内容存档于2018-12-31). 
  93. ^ Wait on resources being set in the future. Contribute to adhearsion/future-resource development by creating an account on GitHub. 2013-12-27 [2018-12-31]. (原始内容存档于2018-06-11) –通过GitHub. 
  94. ^ Zero-cost asynchronous programming in Rust. Contribute to rust-lang-nursery/futures-rs development by creating an account on GitHub. 2019-01-04 [2019-01-04]. (原始内容存档于2019-02-26) –通过GitHub. 
  95. ^ Util. twitter.github.io. [2018-12-31]. (原始内容存档于2018-12-23). 
  96. ^ al45tair / Async. bitbucket.org. [2018-12-31]. (原始内容存档于2018-12-31). 
  97. ^ A Swift based Future/Promises Library for IOS and OS X.: FutureKit/FutureKit. 2018-12-31 [2018-12-31]. (原始内容存档于2018-08-10) –通过GitHub. 
  98. ^ Dispatch - Apple Developer Documentation. developer.apple.com. [2019-01-04]. (原始内容存档于2018-12-31). 
  99. ^ FutureLib is a pure Swift 2 library implementing Futures & Promises inspired by Scala.: couchdeveloper/FutureLib. 2018-10-15 [2018-12-31]. (原始内容存档于2018-06-12) –通过GitHub. 
  100. ^ Work with values that haven't been determined yet.: bignerdranch/Deferred. 2019-01-02 [2019-01-04]. (原始内容存档于2018-06-10) –通过GitHub. 
  101. ^ Write great asynchronous code in Swift using futures and promises: Thomvis/BrightFutures. 2019-01-03 [2018-12-31]. (原始内容存档于2018-06-11) –通过GitHub. 
  102. ^ tcl-promise. SourceForge. [2018-12-31]. (原始内容存档于2018-12-31). 
  103. ^ Futures in Go, no package required. [2024-03-08]. (原始内容存档于2024-03-08). 
  104. ^ The Python Standard Library — Networking and Interprocess Communication — asyncio — Asynchronous I/O. [2024-03-11]. (原始内容存档于2019-05-05). 
  105. ^ Kris Zyp. Promise API Proposal. 2009-05-25 [2024-03-20]. (原始内容存档于2024-03-20). 
    CommonJS — Promises. [2024-03-20]. (原始内容存档于2024-03-20). 
  106. ^ Web Workers API. [2024-03-11]. (原始内容存档于2024-05-21). 
    Worker threads API. [2024-03-25]. (原始内容存档于2024-05-11). 
    threads.js — Make web workers & worker threads as simple as a function call. [2024-03-25]. (原始内容存档于2024-05-09). 
  107. ^ Robust promises with Dojo deferred, Site Pen, 2010-05-03 [2018-12-31], (原始内容存档于2018-12-31) 
  108. ^ 108.0 108.1 Alice Manual — Promise. [2024-03-13]. (原始内容存档于2024-04-17). 
  109. ^ 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."
  110. ^ Control Concurrent MVar, Haskell, [2018-12-31], (原始内容存档于2009-04-18) 
  111. ^ Promise, E rights, [2018-12-31], (原始内容存档于2018-12-31) 
  112. ^ Promise Pipelining. www.erights.org. [2018-12-31]. (原始内容存档于2018-10-22). 
  113. ^ Promise Pipelining on the C2 wiki. [2018-12-31]. (原始内容存档于2005-09-25). 
  114. ^ WaitNeeded, Mozart Oz, [2018-12-31], (原始内容存档于2013-05-17) 

外部链接

编辑