clojure core.async通道如何清理?

clojure core.async通道如何清理?,clojure,core.async,Clojure,Core.async,我第一次看到Clojure core.async,并经历了Rich Hickey的精彩演示: 我对他在演讲结束时展示的例子有一个问题: 根据Rich的说法,这个示例基本上是尝试获取特定查询的web、视频和图像结果。它对每一个结果并行地尝试两个不同的源,并且只为每个结果提取最快的结果。整个操作所需时间不超过80ms,因此,如果我们无法在80ms内获得图像结果,我们将放弃。“最快”函数创建并返回一个新频道,并启动两个go进程,以检索结果并将其放入频道。然后,我们只需从“最快”通道中取出第一个结果,

我第一次看到Clojure core.async,并经历了Rich Hickey的精彩演示:

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

根据Rich的说法,这个示例基本上是尝试获取特定查询的web、视频和图像结果。它对每一个结果并行地尝试两个不同的源,并且只为每个结果提取最快的结果。整个操作所需时间不超过80ms,因此,如果我们无法在80ms内获得图像结果,我们将放弃。“最快”函数创建并返回一个新频道,并启动两个go进程,以检索结果并将其放入频道。然后,我们只需从“最快”通道中取出第一个结果,然后将其放到c通道中


我的问题:在我们取得第一个结果后,这三个临时的、未命名的“最快”频道会发生什么?大概还有一个go进程停在那里,试图把第二个结果放到频道上,但是没有人在听,所以它永远不会真正完成。而且,由于频道从未绑定到任何东西,我们似乎再也没有办法用它做任何事情了。go流程和频道是否会“意识到”没有人再关心他们的结果并清理自己?或者我们实际上只是在代码中“泄漏”了三个通道/go进程?

假设由
faster
生成的通道只返回最快查询方法的结果,然后关闭

如果产生了第二个结果,您的假设可能是
最快的
进程被泄漏。他们的成果永远不会被消耗掉。如果他们依靠所有的结果来终止,他们不会终止

请注意,如果在
alt子句

解决此问题的通常方法是在最后一个
go
块中使用
close关闭通道
c
。对封闭通道的看跌期权将被放弃,然后生产商可以终止

这个问题也可以在
faster
的实现中解决。在
faster
中创建的进程本身可以通过
alts进行put
超时
并在一定时间内未使用生成的值时终止

我猜Rich在幻灯片中没有提到这个问题,而是选择了一个不太长的例子。

没有泄漏

停驻的
go
s连接到它们试图在其上执行操作的通道,并且除此之外没有独立存在。如果其他代码对某个
go
停在的频道失去兴趣(注意,
go
如果停在
alt!
/
alts!
上,它可以同时成为许多频道的推杆/接球手),那么最终它将与这些频道一起被GC


唯一需要注意的是,要成为GC'd,
go
s实际上必须先停车。因此,任何不停地循环做事情而不停车的
/
/
alt!
/
alts!
/
alts!
)都将永远存在。不过,很难意外地编写此类代码。

除了警告和异常之外,您可以在REPL上测试JVM上的垃圾收集

例如:

(需要“[clojure.core.async:as async]”
=>零
(def c(异步/信道))
=>#用户/c
(定义d(异步/执行循环[])
(当让[v(异步/#'用户/d
(异步/>c:hi)
=>正确
:hi;core.async go块正在工作
(导入java.lang.ref.WeakReference)
=>java.lang.ref.WeakReference;在不阻止垃圾收集的情况下保留引用
(定义e(WeakReference.c))
=>#“用户/e
(定义f(WeakReference.d))
=>#“用户/f
(.get e)
=>#对象[…]
(.get f)
=>#对象[…]
(def c无)
=>#用户/c
(def d无)
=>#“用户/d
(println“我们需要在REPL中清除*1、*2和*3。”)
我们需要清除REPL中的*1、*2和*3。
=>零
(打印项次*1*2*3)
零用户/d用户/c
=>零
(系统/一般事务委员会)
=>零
(.get e)
=>零
(.get f)
=>零
刚才发生了什么?我设置了一个go块并检查它是否正常工作。然后使用a观察通信通道(c)和go块返回通道(d)。然后我删除了对c和d的所有引用(包括我的REPL创建的
*1
*2
*3
),请求垃圾收集,(幸运的是,他们没有做出强有力的保证),然后发现我的弱引用已经被清除


在本例中,至少在删除对相关通道的引用后,这些通道会被垃圾收集(不管我是否关闭了它们!)

嗯,好的。我现在从你和Leon那里得到了两个相互矛盾的答案。你能为你的声明提供参考吗?是的,请链接到实现细节。另外,请在上面的代码示例中解释这将如何工作。例如,在L4中使用go块:假设
c
正在阻止put。
faster
进行秒d put未被消耗。在上面的代码示例中,
c
faster
返回的通道何时被垃圾收集?嗯,好吧。我现在从你和Michal那里得到了两个相互矛盾的答案。你能为你的声明提供参考吗?可能Michal关于go块的声明是正确的。唉我们不知道
faster
的实现是否使用go块。如果它产生线程(这很可能发生在并发搜索查询的情况下),并通过
!!
执行阻塞PUT,这些被放弃的线程将永远留在线程池中,直到JVM在足够的请求后死亡。这在例如,该演示文稿的示例代码只调用(go…(sleep…)done),因此在回答这个使用问题时,它没有任何用处。严格来说,
(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