Clojure - 为什么在阻塞插入通道时执行会挂起? (core.async)

时间:2014-01-04 22:34:37

标签: clojure core.async

请考虑以下代码段:

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

执行此操作将永远挂起。那是为什么?

如果我改为(go (>! c "hello")),它就可以了。

2 个答案:

答案 0 :(得分:15)

要进行异步放置,请使用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中 - 例程或使用put!

(go (>! ch v))是否等同于(put! ch v)

是的,但不一样。 put!是围绕core.async.impl.protocols/WritePort put!方法的渠道实施的API包装器。 (go (>! ch v))的宏扩展最终会在同一个方法调用中发生,但会将其包含在许多生成的状态机代码中,以便可能停止放置操作并暂停执行go - 例程,直到消费者准备好从ch开始(尝试自己(macroexpand `(go (>! ch v))))。产生一个go-block只进行一次异步推送操作是一种浪费,并且比立即调用put!更糟糕。 go生成并返回一个额外的通道,您可以从中获取其实体。这使您可以等待在您的示例中不打算执行的执行(针对异步操作)。

答案 1 :(得分:5)

该频道没有缓冲区,>!!正在阻止。有关此确切情况,请参阅the examples。由于这个原因,它们产生了第二个线程 - 以防止阻塞主线程。使用goroutine,就像你的问题一样,按照类似的原则运作。

您还可以使用一些缓冲区空间创建频道 - (chan 1)