如何在单线程中执行一些 Clojure 期货?

How to execute some Clojure futures in a single thread?

我想在 Clojure 中创建一些 futures 并 运行 它们都在特定线程上,以确保它们一次 运行 一个。这可能吗?

包装 Java 库来执行此操作并不难,但在我这样做之前,我想确保我没有错过 Clojure 的实现方式。在 Java 中,我可以通过实现 FutureTask 并将这些任务提交给单线程执行程序来实现。

Clojure 的 future macro calls future-call function which uses a dedicated executor service。这意味着您无法控制强制顺序执行。

另一方面,您也可以使用 promise instead of future objects and one future thread to sequentially deliver the results. Promise's API is similar to what futures provide. They have deref and realized?

以下代码示例在后台的新线程上按顺序执行子任务,而函数立即返回的结果包含对计算值的承诺。

(defn start-async-calc []
  (let [f1 (promise)
        f2 (promise)
        f3 (promise)]
    (future
      (deliver f1 (task-1))
      (deliver f2 (task-2))
      (deliver f3 (task-3)))
    {:task1 f1
     :task2 f2
     :task3 f3}))

如果您想按顺序调用 future,您可以像这样手动使用它:

(do @(future 1)
    @(future 2)
    @(future 3))

它们仍然可能在不同的线程中调用,但下一个线程不会在前一个线程完成之前被调用。这是由 @(或 deref 函数)保证的。这意味着您执行 do 表单的线程将在完成之前被上一个承诺阻塞,然后产生下一个。

你可以用这样的宏来美化它:

(defmacro sequentialize [& futures]
  `(do ~@(map #(list `deref %) futures)))

user> (let [a (atom 1)]
        (sequentialize
         (future (swap! a #(* 10 %)))
         (future (swap! a #(+ 20 %)))
         (future (swap! a #(- %))))
        @a)
;;=> -30

这与手册 do 完全相同。请注意,即使某些线程 运行 更长:

,对 a 原子的突变也是有序的
user> (let [a (atom 1)]
        (sequentialize
         (future (Thread/sleep 100)
                 (swap! a #(* 10 %)))
         (future (Thread/sleep 200)
                 (swap! a #(+ 20 %)))
         (future (swap! a #(- %))))
        @a)
;;=> -30

Manifold provides a way to create future with specific executor。它不是核心 Clojure 库的一部分,但它仍然是一个高质量的库,如果您需要比核心库提供的更多灵活性来处理未来(不求助于 Java 互操作),它可能是最佳选择。

除了promises提到的,还可以使用delay。 Promises 有一个问题,您可能会意外地不交付它们,并创建一个 futures 和 delays 不可能发生的死锁场景。未来和延迟之间的区别仅在于执行工作的线程。有了 future,工作在后台完成,延迟后,工作由第一个尝试取消引用它的线程完成。所以如果 future 比 promises 更合适,你总是可以这样做:

(def result-1 (delay (long-calculation-1)))
(def result-2 (delay (long-calculation-2)))
(def result-3 (delay (long-calculation-3)))

(defn run-calcs []
  @(future
     @result-1
     @result-2
     @result-3))