如何清理 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 go
s 附加到他们试图执行操作的频道,除此之外没有独立存在。如果其他代码对某个 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已经被清除了
至少在这种情况下,一旦删除了对所涉及频道的引用,这些频道就会被垃圾回收(不管我关闭它们的失败!)
我是第一次看 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 go
s 附加到他们试图执行操作的频道,除此之外没有独立存在。如果其他代码对某个 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已经被清除了
至少在这种情况下,一旦删除了对所涉及频道的引用,这些频道就会被垃圾回收(不管我关闭它们的失败!)