我有两个返回布尔值的未来。我想要做的事情基本上是这样的:
(if (or @future1 @future2)
...)
但是,如果优化后的代码中任何一个 future 先完成且其返回值为 true,则无需等待剩余的 future 完成;直接进行下一步操作即可。当然,如果返回值为 false,则需要等待剩余 future 完成后再执行。是否有一种简单的方式来实现这个效果呢?
(if (or @future1 @future2)
...)
但是,如果优化后的代码中任何一个 future 先完成且其返回值为 true,则无需等待剩余的 future 完成;直接进行下一步操作即可。当然,如果返回值为 false,则需要等待剩余 future 完成后再执行。是否有一种简单的方式来实现这个效果呢?
一般情况下,你可以向两个投递人做出同样的承诺。例如:
(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)
时,只要第一个 deliver
发生,就会随机产生 :a
或 :b
;另一个 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)
,你会看到预期的结果,特别是当 :or
为 true
时,有时 :a?
或 :b?
将为 false
,但如果 :or
为 false
,两者都将始终为 true
。
or
。最短的执行时间是在第一个未来在第二个未来完成之前产生true
时,最长的执行时间是在第一个未来产生false
时,并且第二个未来需要更多时间才能完成。 - Alex Taggart现在,使用core.async
可以实现这一点,具体方法见我回答的有关Clojure线程处理长时间运行过程并比较它们返回值的问题。那个回答定义了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)))
thread-or
变体中,当第一个真值出现时短路,你可以简单地返回这个值,然后对其使用 core.match。如果你想要一个 thread-and
,并在最后使用core.match来计算任何一个值,那么你就必须将第一个返回值存储在本地循环变量中(将其作为第二个循环变量 res1
添加进去,初始值为nil
,然后 (recur ... (if (nil? res1) result res1))
,...
如前所述)。 - Michał Marczykthread-and
返回它。 (除非您想将匹配线路连接到thread-and
;或者您可以重写它,以便您可以从外部传递该逻辑作为函数。) - Michał Marczykthread-or
的;最初我本来想这么做的,但出于某种原因我却粘贴了 thread-and
。 - Michał Marczykthread-and
。对于造成的混淆,我感到抱歉。 - Michał Marczyk我非常喜欢这个问题。也许我感到惊讶的是,这是一个非常简单的新颖问题。无论如何,撇开口水不谈,我认为我有一些可以适用于您的东西。
与其这样做:
(if (or @future1 @future2)
...)
做:
(if (or (and (realized? future1) @future1)
(and (realized? future2) @future2))
...)
(defmacro future-or [& futures]
`(or ~@(for [f futures]
`(and (realized? ~f)
(deref ~f)))))
(if (future-or future1 future2)
...)
(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
来重复循环,直到以下两种情况之一发生:要么其中一个future已经实现且为true
,此时循环将返回true
;要么所有的futures都已实现且全部为false
,此时循环将返回false
。在它们的父级(and ...)
表达式末尾添加(not ...)
表达式是很重要的,这样你就不会被困在检查任何future是否为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
,而不是or
。在某种程度上,我改变了行为,以获取一组未来(而不是单个参数的可变数量-some
和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))))))
(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"