Clojure中的future和promise有何不同?

91

未来对象 (futures) 和承诺对象 (promises) 都会在计算其值之前阻塞,所以它们之间有什么区别呢?


11
我不确定为什么这个问题被踩了,难道在提问之前就不知道答案的问题现在是不好的事情吗? - JUST MY correct OPINION
我没有给任何答案打-1分。我们如何知道是谁在问题或答案上打了-1分? - yazzapps.com
1
你和我都不能,Zubair。我只是好奇谁在你的问题上给了一个-1,因为这是一个完全合理的问题,并且绝对符合SO的主题。 - JUST MY correct OPINION
6个回答

57

以Clojure的术语回答,以下是来自Sean Devlin的视频演示的一些示例:

(def a-promise (promise))
(deliver a-promise :fred)

(def f (future (some-sexp)))
(deref f)
请注意,在 Promise 中,你明确地传递了稍后计算中选择的值(在本例中为 :fred)。另一方面,Future 是在创建它的同一位置被使用。some-expr 可能会在幕后启动并同时计算,但如果到访问时它仍未评估,则线程将一直阻塞,直到它可用。
编辑以添加
为了更进一步区分 Promise 和 Future,请注意以下内容:
Promise 1. 创建一个 Promise。现在,该 Promise 对象可以传递给任何线程。 2. 继续计算。这些可以是涉及副作用、下载数据、用户输入、数据库访问、其他 Promises 等的非常复杂的计算 - 无论您喜欢什么类型的代码,都会看起来非常像您在任何程序的主要代码线中所看到的。 3. 当您完成时,可以向该 Promise 对象提供结果。 4. 在您完成计算之前,任何试图 deref 您的 Promise 的项都将阻塞,直到您完成。一旦您完成并 deliver 承诺,承诺将不再阻塞。
Future 1. 创建您的 Future。您的 Future 的一部分是用于计算的表达式。 2. 未来可能或可能不会并发执行。它可以被分配一个线程,可能来自线程池。它可能只是等待并且什么也不做。 从您的角度来看,您无法确定。 3. 在某个时刻,您(或另一个线程)deref 您的 Future。如果计算已经完成,则会获取其结果。如果它尚未完成,则会阻塞,直到完成。(假设如果它尚未开始,则 deref 它意味着它开始执行,但这也不是保证。) 虽然你可以将 future 中的表达式设计得和创建 promise 后跟随的代码一样复杂,但这不太可取。这意味着 futures 更适合快速、可以在后台处理的计算,而 promises 更适合于庞大而复杂的执行路径。此外,就可用的计算而言,promises 似乎更灵活,更偏向于 promise 创建者进行工作,而另一个线程则收获成果。而 futures 更偏向于自动启动一个线程(没有繁琐和容易出错的开销)并继续进行其他事情,直到您 - 发起线程 - 需要结果。

但是在承诺或未来完成之前,您可以拥有任何计算块。即:(@a + @b)将在未来和承诺中都起作用。 - yazzapps.com
2
一个 Promise 对我来说似乎更加灵活。我创建了一个 Promise,将其传递给另一个线程。然后我可以进行许多复杂的计算,包括等待 I/O、从互联网下载数据、等待用户输入等等。当所有工作都完成时,我会使用结果值交付 Promise。Future 只涵盖一个 S 表达式。它可能是非常复杂的 S 表达式,但那样会有点...刻板。此外,Future 会在一个线程(或池)中自动执行其工作。如果要在 Promise 中执行相同的操作,则需要更多的工作。 - JUST MY correct OPINION
就我所知,由于单个s表达式可以调用任意代码路径,因此它并不一定与你可以塞入表达式中的代码量有关。因此,与其说承诺“更灵活”,不如说它的目的只是不同而已。否则,为什么要同时存在两者呢? - Zefira
2
只是为了记录,future调用的主体可以包括N个sexpr。 - deprecated
这个解释应该是Clojure文档的一部分。 - Piyush Katariya

27

Future和Promise是从生产者向消费者传递异步计算结果的机制。

对于Future计算在创建Future时就已经定义好,并且异步执行会尽快开始。它还“知道”如何生成异步计算。

对于Promise计算启动时间和[可能的] 异步调用与传递机制解耦。当计算结果可用时,生产者必须显式调用deliver,这也意味着生产者控制结果何时变为可用。

对于Promises,Clojure 在使用相同对象(调用promise返回的对象)来生产(deliver)和消费(deref计算结果方面存在设计错误。这两个能力是非常不同的,应该分别处理。


假设您创建了一个Promise并将其提供给3个客户端。 没有任何阻止其中一个客户端使用虚假结果解决它并向另外两个客户端发出信号。 此外,您将无法再解决此Promise。 相反,如果您拥有Promise + Resolver对,将Promise提供给客户端并将Resolver保留给自己,则此场景变得不可能。 有关更多信息,建议搜索“基于能力的访问控制”和“基于能力的安全性”。 - Dimagog
1
我不确定将安全性与如此简单的引用类型(请查看其实现)promise耦合是否方便。'恶意'消费者很少见;您可以在承诺之上构建自己的抽象,没有任何限制。 - deprecated
9
这不是关于安全性的问题,只是能力基础编程通常与安全性有关。在这里,重点是代码正确性。通常使用的术语是“通过构建正确”,问题是“你能构建一个不正确的程序吗”?不是有意为之,而是出于意外。使用单个Promise对象可以实现此目的,而使用两个独立的对象则不行。 - Dimagog
你可以返回一个无法实现的promise,如果这是你想要的话:(defn undeliverable-promise [] (let [p (promise)] (reify clojure.lang.IDeref (deref [_] (deref p)) clojure.lang.IBlockingDeref (deref [_ ms val] (deref p ms val)) clojure.lang.IPending (isRealized [_] (.isRealized p)) clojure.lang.IFn (invoke [_ _] nil)))) - tapichu
指出计算机制是如何解耦的差异,使得这篇文章成为了一个非常简明扼要的解释。谢谢! - synthomat

4
已经有很好的答案了,这里只添加一个“如何使用”的总结:
两者:
创建 promise 或 future 立即返回引用。该引用在 @/deref 上阻塞,直到其他线程提供计算结果。
Future:
创建 future 时,您提供一个同步作业来完成。它在专用无界池中的线程中执行。
Promise:
创建 promise 时不需要参数。应将引用传递给其他“用户”线程,该线程将传递结果。

1
我认为《Clojure for the Brave》第9章最好地解释了delayfuturepromise之间的区别。
统一这三个概念的想法是:任务生命周期。一个任务可以被看作经历三个阶段:定义任务、执行任务、使用任务结果。
一些编程语言(如JavaScript)有类似命名的构造(如JS的Promise),它们将任务生命周期中的几个(或全部)阶段耦合在一起。例如,在JS中,如果不提供计算其值的函数(任务)或立即使用常量值resolve它,就无法构建Promise对象。
然而,Clojure避免了这种耦合,因此它有三个单独的构造,每个构造对应于任务生命周期中的一个阶段。
  1. delay:任务定义
  2. future:任务执行
  3. promise:任务结果
每个构造函数都关注自己的任务生命周期阶段,因此将高阶构造函数(如JS的Promise)解开并分离为其正确的部分。
现在我们看到,在JavaScript中,Promise是上述所有三个Clojure构造函数的组合体。例如:
const promise = new Promise((resolve) => resolve(6))

让我们来分解一下:

  1. 任务定义: resolve(6) 是该任务。
  2. 任务执行:这里有一个隐含的执行上下文,即该任务将在事件循环的未来周期中运行。你无法干预它; 例如,你不能强制要求该任务同步解决,因为异步性已经内置到了Promise中。请注意,在构造Promise时,你已经安排好了任务运行的时间(在某个未指定的时间)。你无法说“让我将其传递给系统中的另一个组件,让它决定何时运行此任务”。
  3. 任务结果:任务的结果包含在Promise对象中,并可以通过thenawait获得。没有办法创建一个“空”的承诺结果,以便由你系统中的尚未知部分填写; 你必须同时定义任务并安排其执行。
PS:Clojure所施加的分离允许这些构造承担它们如果紧密耦合将不适合的角色。例如,一个Clojure的promise,由于已经与任务定义和执行分离,现在可以被用作线程间传输的单元。

1
在Clojure中,promisefuturedelay都是类似于Promise的对象。它们代表了客户端可以通过使用deref(或@)等待的计算结果。客户端重复使用结果,以便不会多次运行计算。
它们在执行计算的方式上有所不同:
  • future将在不同的工作线程中启动计算。 deref将阻塞,直到结果准备就绪。

  • delay将在第一个客户端使用derefforce时惰性地执行计算。

  • promise提供最大灵活性,因为可以使用deliver以任何自定义方式传递其结果。当futuredelay都不符合您的用例时,可以使用它。


-4

首先,一个 Promise 是一个 Future。我认为你想知道 PromiseFutureTask 之间的区别。

Future 表示当前未知但将来会知道的值。

FutureTask 表示将在未来某个时刻计算的结果(可能在某个线程池中)。当您尝试访问结果时,如果尚未发生计算,则会阻塞。否则,结果会立即返回。没有其他参与方参与计算结果,因为计算是事先由您指定的。

Promise 表示一个结果,将由 promiser 在未来交付给 promisee。在这种情况下,您是 promisee,而 promiser 是给您 Promise 对象的人。与 FutureTask 类似,如果在 Promise 被实现之前尝试访问结果,则会被阻止,直到 promiser 实现了 Promise。一旦 Promise 被实现,您总是立即获得相同的值。与 FutureTask 不同,这里涉及另一个参与方,即使承诺的制造者。该另一方负责进行计算并实现 Promise

从这个意义上说,FutureTask 是您向自己做出的 Promise


你确定 Promise 是 Future 吗?我找不到它实现了接口。https://github.com/richhickey/clojure/blob/a1eff35124b923ef8539a35e7a292813ba54a0e0/src/clj/clojure/core.clj#L5489 - Mikael Sundberg
请原谅,我误按了回车键。我的问题已经改变。 - Mikael Sundberg
我的回答是一般性的,不是特定于Clojure的。 - Abhinav Sarkar
9
用Java代码回答关于Clojure的问题似乎有点异想天开。 - JUST MY correct OPINION

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接