我有两个期货可以解决布尔运气问题。我基本上想做像
这样的事情(if (or @future1 @future2)
...)
但是,通过优化,无论哪一个完成,如果它是真的,那么我不会等待剩余的未来完成;去吧。当然,如果值为false,则等待剩余的未来完成。有这么简单的方法吗?
答案 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
会随机产生:b
或deliver
;另一个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?
将为{{1}如果false
为true
,则两者始终为: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
。在实物中,我改变了行为以获取期货的集合(而不是可变数量的单个参数 - 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))))))
表达式放在每个(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"