在Clojure中了解未来和doall

时间:2016-07-06 09:52:25

标签: clojure

我在Clojure by example

中看到了一个关于期货的例子
(let [sleep-and-wait
         (map (fn [time]
           (future
             (Thread/sleep time)
             (println (str "slept " time " sec" ))))
               [4000 5000])]
     (doall (map deref sleep-and-wait))
     (println "done"))

由于map生成了一个懒惰的序列,我希望future没有启动,直到我们在其上调用deref。预计deref将阻止,直到将来返回结果。我们按顺序排列map个元素,所以我希望这个代码运行9秒,但它运行在5。

有人可以解释原因吗?

2 个答案:

答案 0 :(得分:3)

Clojure lazy-seqs并没有承诺最大限度地懒惰。

+user=> (take 1 (for [i (range 1000)] (doto i (println " - printed"))))
(0  - printed
1  - printed
2  - printed
3  - printed
4  - printed
5  - printed
6  - printed
7  - printed
8  - printed
9  - printed
10  - printed
11  - printed
12  - printed
13  - printed
14  - printed
15  - printed
16  - printed
17  - printed
18  - printed
19  - printed
20  - printed
21  - printed
22  - printed
23  - printed
24  - printed
25  - printed
26  - printed
27  - printed
28  - printed
29  - printed
30  - printed
31  - printed
0)

当在向量上调用seq时(大多数惰性操作在其集合参数上隐式调用seq),它会生成一个分块数据类型,它一次实现批量结果,而不是一个接一个地实现。如果您需要控制数据的消耗,您可以做一些强制取消分块的事情。

+user=> 
(defn unchunk [s]
  (when (seq s)
    (lazy-seq
      (cons (first s)
            (unchunk (rest s))))))
#'user/unchunk
+user=> (take 1 (for [i (unchunk (range 1000))] (doto i (println " - printed"))))
(0  - printed
0)

当然,在这种情况下更简单的选择是使用非分块的类型

+user=> (take 1 (for [i (take 1000 (iterate inc 0))] (doto i (println " - printed"))))
(0  - printed
0)

答案 1 :(得分:0)

这可能与map函数的性质有关:它不是逐个采用映射集合的元素,而是通过块进行优化。这是一个小例子:

user> (defn trace [x]
        (println :realizing x)
        x)
#'user/trace

user> (def m (map trace (range 1000)))
#'user/m

user> (first m)
:realizing 0
:realizing 1
:realizing 2

...
:realizing 30
:realizing 31
0

所以在你调用map的情况下,它不会在一个单独的线程中启动一个未来,而是启动它们,因此你只需要阻塞直到运行时间最长的线程完成(并且它持续5秒)