在clojure中短路期货

时间:2013-06-01 01:43:28

标签: asynchronous clojure future promise

我有两个期货可以解决布尔运气问题。我基本上想做像

这样的事情
(if (or @future1 @future2) 
  ...)

但是,通过优化,无论哪一个完成,如果它是真的,那么我不会等待剩余的未来完成;去吧。当然,如果值为false,则等待剩余的未来完成。有这么简单的方法吗?

4 个答案:

答案 0 :(得分:4)

在一般情况下,您可以向两个提供者提供相同的承诺。 E.g:

(defn foo []
  (let [p (promise)]
    (future
      (Thread/sleep (rand-int 1000))
      (deliver p :a))
    (future 
      (Thread/sleep (rand-int 1000))
      (deliver p :b))
    @p))

第一个(foo)出现后,调用:a会随机产生:bdeliver;另一个deliver将是无操作。

根据您的具体情况,您需要返回两个布尔值。我能想到的唯一一件事(而且有点混乱)就是使用交付者之间共享的第三个承诺:

(defn foo []
  (let [a (promise)
        b (promise)
        link (promise)]
    (future
      (Thread/sleep (rand-int 5000))
      (let [res (rand-nth [true false])]
        (deliver link res)
        (deliver a res)))
    (future
      (Thread/sleep (rand-int 5000))
      (let [res (rand-nth [true false])]
        (deliver link res)
        (deliver b res)))
    {:or (or @link @a @b)  
     :a? (realized? a) 
     :b? (realized? b)
     :link @link
     :a @a 
     :b @b}))
  • 如果a首先发送true,则or会立即完成。
  • 如果a首先发送false@a会立即返回,然后会阻止@b
  • 如果b首先发送true,则or会立即完成。
  • 如果a首先提供false,则会阻止@a

反复调用(foo),您应该看到预期结果,具体而言,当:ortrue时,有时:a?:b?将为{{1}如果falsetrue,则两者始终为:or

答案 1 :(得分:3)

现在可以使用core.async进行此操作,如我对新with Clojure threading long running processes and comparing their returns问题的回答中所述。该答案定义thread-and;这个问题需要thread-or

(defn thread-or
  "Call each of the fs on a separate thread. Return logical
  disjunction of the results. Short-circuit (and cancel the calls to
  remaining fs) on first truthy value returned."
  [& fs]
  (let [futs-and-cs
        (doall (for [f fs]
                 (let [c (chan)]
                   [(future (>!! c (f))) c])))]
    (loop [futs-and-cs futs-and-cs]
      (let [[result c] (alts!! (map peek futs-and-cs))]
        (if result
          (do (doseq [fut (map first futs-and-cs)]
                (future-cancel fut))
              result)
          (let [new-futs-and-cs (remove #(identical? (peek %) c)
                                        futs-and-cs)]
            (if (next new-futs-and-cs)
              (recur new-futs-and-cs)
              (<!! (peek (first new-futs-and-cs))))))))))

测试(经常是假的)和(经常是真的):

(thread-or (constantly true) (constantly true))
;;= true

(thread-or (constantly true) (constantly false))
;;= true

(thread-or (constantly false) (constantly false))
;;= false

另请注意,短路确实有效:

;; prints :foo before returning true
(thread-or #(do (Thread/sleep 3000) true)
           #(do (Thread/sleep 1000) (println :foo)))

;; does not print :foo
(thread-or #(do (Thread/sleep 3000) true)
           #(do (Thread/sleep 7000) (println :foo)))

答案 2 :(得分:1)

我非常喜欢这个问题。也许我很惊讶看到一个如此简单的新问题。无论如何,流口水,我认为我有一些适合你的东西。

而不是:

(if (or @future1 @future2)
  ...)

执行:

(if (or (and (realized? future1) @future1)
        (and (realized? future2) @future2))
  ...)

诀窍在于在询问是true还是false之前测试某些事情是否已实现。

这可以推广到像这样的宏:

(defmacro future-or [& futures]
  `(or ~@(for [f futures]
          `(and (realized? ~f)
                (deref ~f)))))

然后使用像:

(if (future-or future1 future2)
  ...)

等一下。也许我错误地理解了你的问题。也许你想要阻止执行,直到其中一个期货完成并返回true,在这种情况下你执行if的then子句,或者你的两个期货都已完成而且都没有返回{{ 1}},在这种情况下,你执行true的else子句。那是一个不同的故事。

我能想出的最简洁的方式并不是很漂亮,但也不是很长:

if

现在,这使用(if (loop [] (cond (or (and (realized? future1) @future1) (and (realized? future2) @future2)) true (and (realized? future1) (realized? future2) (not @future1) (not @future2)) false :else (recur))) ...) 重复循环,直到发生以下两种情况之一:其中一个期货已实现且loop,在这种情况下,循环返回true ;或者所有期货都已实现且所有期货都已true,在这种情况下,循环将返回false。将false表达式放在父(not ...)表达式的末尾非常重要,这样您就不会检查是否有任何期货(and ...)true都实现了。

这个新的解决方案可以概括为:

false

以与上述(defmacro future-or [& futures] `(loop [] (cond (or ~@(for [f futures] `(and (realized? ~f) (deref ~f)))) true (and ~@(for [f futures] `(realized? ~f)) ~@(for [f futures] `(not (deref ~f)))) false :else (recur)))) 示例相同的方式使用。

现在,我对优化几乎一无所知。但据我所知,这肯定不如理论上那么有效,因为一旦任何给定的未来实现,就没有必要不止一次地测试它的价值。好吧,这里有两个解决方案,我将其命名为future-or。由于正在测试的期货必须在每次循环迭代后动态地改变,我必须使它成为一个函数。这样,这种新方法类似于future-some,而不是some。在实物中,我改变了行为以获取期货的集合(而不是可变数量的单个参数 - orsome之间的另一个差异)。一种解决方案不会仔细检查(我认为):

or

这里有很多细节,但要点是:如果没有期货要测试,它会返回(defn future-some [futures] (if (empty? futures) false (let [res (reduce (fn [unrealized f] (if (realized? f) (if @f (reduced true) unrealized) (cons f unrealized))) () futures)] (if (true? res) true (recur res))))) ,否则,它会在期货列表中进行迭代,测试是否有任何未实现的期货。如果实现了未来,并且还解除引用false,则迭代会中断以返回true。如果未来已实现但未取消引用true,则迭代将继续到下一个项目。如果未来未实现,则会添加一个列表,以便在true的下一次递归中使用。

另一种解决方案更简洁,但不太理想:

future-some

与另一个类似,除了它过滤掉已实现的第一个,然后测试,然后再次过滤(这次反向 - 得到未实现的),如果它需要重复。第二次过滤是效率低下的步骤。

我提出的所有解决方案的一个问题是,未来的取消会在解除引用时导致错误,而它们可能只是简单地继续,好像未来是假的。这可以通过在任何解除引用之前将(defn future-some [futures] (if (empty? futures) false (let [realized (filter realized? futures)] (if (some deref realized) true (recur (remove (set realized) futures)))))) 表达式放在每个(not (future-cancelled? ...))表达式中来解决。或者,对于(and ...)函数,您必须将future-some谓词替换为realized?,或将(some-fn (comp not future-cancelled?) realized?)替换为胆小的人。

再次,说真的,谢谢你提出这个问题。

答案 3 :(得分:0)

假设您不想每隔X毫秒轮询期货,并且您无法控制期货/线程的创建或它们正在执行的fn,那么解决方案就是创建更多线程,每个线程线程等待未来:

(defn wait-for-any [& futures]
  (let [how-many-left (atom (count futures))
        p (promise)
        wait-and-notify (fn [f]
                         (fn []
                           (if @f
                             (deliver p true)
                             (when (zero? (swap! how-many-left dec))
                               (deliver p false)))))]
    (dorun (map (comp future-call wait-and-notify) futures))
    @p))

 (defn sleep-and-return [what-to-return sleep-time]
  (future
    (Thread/sleep sleep-time)
    what-to-return))


(time (println (wait-for-any
                (sleep-and-return false 1000)
                (sleep-and-return false 2000)
                (sleep-and-return false 3000)
                (sleep-and-return false 4000)
                (sleep-and-return false 5000))))

>>>false
>>>"Elapsed time: 5000.933906 msecs"

(time (println (wait-for-any
                (sleep-and-return false 1000)
                (sleep-and-return true 2900)
                (sleep-and-return true 3000)
                (sleep-and-return false 4000)
                (sleep-and-return false 5000))))
>>>true
>>>"Elapsed time: 2901.309695 msecs"