8

I've got two futures that resolve to booleans. I basically want to do something like

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

but, with the optimization that whichever one finishes first, if it's true, then I don't wait for the remaining future to finish; just go. Of course if the value is false, then wait for the remaining future to finish. Is there a straightforward way of doing this?

4

4 回答 4

4

In the general case, you can give the same promise to the two deliverers. 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))

Calling (foo) will randomly yield :a or :b as soon as the first deliver occurs; the other deliver will be a no-op.

To your specific case, you're requiring two booleans be returned. The only thing I can think of (and it's a bit messy) would be to use a third promise shared between the deliverers:

(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}))
  • If a delivers true first, the or completes immediately.
  • If a delivers false first, the @a returns immediately, then blocks on @b.
  • If b delivers true first, the or completes immediately.
  • If a delivers false first, it blocks on @a.

Repeatedly invoke (foo) and you should see expected results, specifically, when :or is true, then sometimes :a? or :b? will be false, but both will always be true if :or is false.

于 2013-06-01T02:08:14.547 回答
3

正如我对新的 Clojure 线程长时间运行进程并比较它们的返回core.async问题的回答中所描述的,这现在是可能的。这个答案定义了;这个问题要求:thread-andthread-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))))))))))

用 (constantly false) 和 (constantly true) 进行测试:

(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)))
于 2013-07-12T21:47:50.353 回答
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,在这种情况下您执行您的 then 子句if,或者您的两个期货都完成并且都不返回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 ...),这样您就不会卡在检查是否有任何未来truefalse直到它们全部实现。

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

(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?), 或#(and (not (future-cancelled %)) (realized? %))用于胆小的人。

再次,说真的,谢谢你的问题。

于 2013-06-02T07:30:22.683 回答
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"
于 2013-06-15T01:21:56.433 回答