Clojure - 为什么在向通道中进行阻塞插入时会出现执行停滞?(core.async)

9
考虑以下代码片段:

请看下面的代码:

(let [chs (repeatedly 10 chan)]
  (doseq [c chs]
    (>!! c "hello"))
  (doseq [c chs]
    (println (<!! c))))

执行这个会一直卡住,这是为什么呢?

如果我改成 (go (>! c "hello")) ,它就能正常工作。

2个回答

15
为了进行异步的put操作,请使用clojure.core.async/put!
(let [chs (repeatedly 10 chan)]
  (doseq [c chs]
    (put! c "hello"))
  (doseq [c chs]
    (println (<!! c))))

这个例子中是可行的,因为由于所有必要的放置都是异步进行的,所以<!!始终会解除阻塞。请注意以下几点:

  • 阻塞作为不同进程之间的同步约束
  • >!!<!!会阻塞主线程。go例程在主线程上运行,但它们的代码通过宏扩展进行修改,以便执行控制被反转并且它们可以按照core.async通道阻塞/缓冲逻辑的规律依次停放/执行。这种技术通常称为IOC(控制反转)状态机。
  • ClojureScript只有一个线程。因此,其对core.async的实现甚至不包含>!!/<!!。如果您编写旨在与ClojureScript兼容的代码,则只在go例程中从通道中取出或将值分派到传递给take!的高阶函数中,并始终在go例程中执行puts或使用put!

(go (>! ch v))(put! ch v)是否等效?

是的,但它们不完全相同。 put!是对core.async.impl.protocols/WritePort的通道实现中put!方法的API包装器。Macroexpansion of (go (>! ch v))最终会产生相同的方法调用,但是会将其包装在大量生成的状态机代码中,以便可能停放放置操作,暂停执行go例程,直到消费者准备好从ch中获取(请尝试自己的(macroexpand `(go (>! ch v))))。仅为执行一次异步放置操作而生成go-block有点浪费,并且比立即调用put!性能更差。 go生成并返回一个额外的通道,您可以从其中获取其主体结果。这使您可以等待其执行完成,但您在示例中没有这样做(目标是异步操作)。


3
(put! c)(go (>! c)) 相等吗? - Mark
1
我编辑了我的回答,希望能够提供更多的见解。 - Leon Grapenthin

5
那个通道没有缓冲区,>!! 正在阻塞。请参考示例,了解确切的情况。他们会为此原因生成第二个线程 - 以防止阻塞主线程。与您的问题中所述相似,使用 goroutine 也是基于类似的原理。
您还可以创建带有一些缓冲空间的通道 - (chan 1)

啊,我忘了puts也是阻塞的。它们会阻塞在什么上面呢?对于gets来说,更直观的是它们会阻塞在通道中存在值时。但是当你put一些东西时,你会阻塞在什么上面呢? - Mark
1
你的问题标题说“阻塞插入”,所以你至少下意识地知道了它。:-) 基本上,它会一直阻塞,直到通道能够接收该值。在这种情况下,这意味着有一个消费者立即准备好获取该值。我不知道具体实现细节,但从概念上讲,没有缓冲区的通道只是作为同步点。而有缓冲区的通道,在发生阻塞之前将保持n个项目。 - Shepmaster
@MarkF 你所说的 puts 是指 >!/>!! 吗?其实有一个名为 put! 的函数也可以实现相同的功能,但不会阻塞调用线程。 - erikprice

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接