如何清理 clojure core.async 频道?

How do clojure core.async channels get cleaned up?

我是第一次看 Clojure core.async,正在阅读 Rich Hickey 的精彩演讲:http://www.infoq.com/presentations/clojure-core-async

我对他在演讲结束时展示的例子有疑问:

根据 Rich 的说法,此示例基本上是尝试获取特定查询的 Web、视频和图像结果。它为每个结果并行尝试两个不同的来源,并为每个结果提取最快的结果。并且整个操作不会超过 80 毫秒,所以如果我们不能得到例如图像在 80 毫秒内生成,我们就放弃。 'fastest' 函数创建和 returns 一个新通道,并启动两个 go 进程竞相检索结果并将其放入通道。然后我们只需从 'fastest' 通道中取出第一个结果并将其拍打到 c 通道上。

我的问题:这三个临时的、未命名的 'fastest' 频道在我们获取第一个结果后会怎样?大概仍然有一个 go 进程被停放,试图将第二个结果放到通道上,但没有人在听,所以它永远不会真正完成。而且由于该频道从未绑定到任何东西,所以我们似乎再也没有办法用它做任何事情了。 go process & channel "realize" 会不会再没人关心他们的结果了,把自己清理干净?或者我们在这段代码中基本上只是 "leak" 三个通道/go 进程?

假设fastest产生的通道仅returns最快查询方法的结果然后关闭。

如果产生了第二个结果,您的假设可能认为 fastest 进程已泄漏。他们的结果永远不会被消耗。如果他们依靠消耗所有结果来终止,他们就不会终止。

请注意,如果在 alt! 子句中选择频道 t,也会发生这种情况。

解决这个问题的通常方法是在最后一个 go 块中用 close! 关闭通道 c。然后将丢弃对封闭通道的放置,然后生产者可以终止。

这个问题也可以在fastest的实现中得到解决。在 fastest 中创建的进程本身可以通过 alts!timeout 进行放置,如果在一定时间内未消耗所产生的值,则终止。

我猜 Rich 没有解决幻灯片中的问题,而是举了一个不太长的例子。

没有泄漏。

Parked gos 附加到他们试图执行操作的频道,除此之外没有独立存在。如果其他代码对某个 go 停放的频道失去兴趣(注意,如果 go 停放在 alt! / alts!),然后最终它会与那些通道一起被 GC。

唯一需要注意的是,为了获得 GC,go 实际上必须先停车。因此,任何 go 一直在循环中做事而不停车(<! / >! / alt! / alts!)实际上将永远存在。不过,很难一不小心就写出这种代码。

抛开警告和例外,您可以在 REPL 中测试 JVM 上的垃圾收集。

例如:

(require '[clojure.core.async :as async])
=> nil

(def c (async/chan))
=> #'user/c
(def d (async/go-loop [] 
         (when-let [v (async/<! c)] 
           (println v) 
           (recur))))
=> #'user/d

(async/>!! c :hi)
=> true
:hi        ; core.async go block is working

(import java.lang.ref.WeakReference)
=> java.lang.ref.WeakReference    ; hold a reference without preventing garbage collection
(def e (WeakReference. c))
=> #'user/e
(def f (WeakReference. d))
=> #'user/f

(.get e)
=> #object[...]
(.get f)
=> #object[...]

(def c nil)
=> #'user/c
(def d nil)
=> #'user/d
(println "We need to clear *1, *2 and *3 in the REPL.")
We need to clear *1, *2 and *3 in the REPL.
=> nil
(println *1 *2 *3)
nil #'user/d #'user/c
=> nil
(System/gc)
=> nil
(.get e)
=> nil
(.get f)
=> nil

刚刚发生了什么?我设置了一个 go 块并检查它是否正常工作。然后用了一个WeakReference to observe the communication channel (c) and the go block return channel (d). Then I removed all references to c and d (including *1, *2 and *3 created by my REPL), requested garbage collection, (and got lucky, the System.gc Javadoc不做强保证)然后观察到我的weak references已经被清除了

至少在这种情况下,一旦删除了对所涉及频道的引用,这些频道就会被垃圾回收(不管我关闭它们的失败!)