请在2024 Clojure状况调查!中分享您的想法。

欢迎!请查看关于页面以了解更多关于此如何工作的信息。

0
IO

通常,我们希望避免阻塞IO并使其变为非阻塞。假设我们通过一个go块非阻塞地保存到数据库。

(defn save-user-info [user]
   (go (...)))

现在,如果我想要使用这个函数,我想在go块中调用它,因为我不想发生阻塞。

(defn register-user [user other-info]
  (go (validate-user ...)
      ...
      (<! (save-user-info user))))

save-userregister-user可以在许多地方使用,然后我们需要另一个go块。

(defn send-welcome-email [user params]
    (go ...
        (<! (register-user user))
        ...))

再次,可以在其他功能(比如last-fn)中使用send-welcome-email,然后我们需要另一个go块。因此,我们创建了一个使用go(就像原始功能一样)的函数链。

然后,如果调用last-fn,必须逐一创建几个go块。

我的问题是:这是否是使用go的正确方式?或者我的问题可能是不正确的。

我很担心,因为这似乎意味着要执行一个函数,我们需要创建多个go(然后是通道)并收集它们。这某种程度上显得不够优雅。我不太理解core.async,但我相信go可以用于非阻塞IO。另一方面,如果我们把go看作一个状态机,那么我们应当避免在go中进行IO,并让事情内部无副作用。

2个答案

+1

已选择(时间)
 
最佳答案

不要在核心函数定义中使用 async/goasync/thread。制造“正常”,单一责任,解耦合,易于测试的函数,然后通过类似输送带式的管道连接它们。就像您使用函数组合一样,但它们通过通道进行通信,而不是直接通信。

您可以将函数直接附加到通道,方法是将其转换为转换器,例如 (async/chan buf (map save-user-info))。您可以通过 async/pipeline(-blocking -async) 等将两个通道连接起来。如果您的函数使用阻塞 I/O 操作(处理数据库、发送电子邮件、读取文件)则使用阻塞构造(!!async/threadasync/pipeline-blocking 等)。如果操作是计算或非阻塞 I/O,则使用停车变化(!async/goasync/pipeline 等)。

+1

一般来说 go 块并不适用于阻塞操作。
事实上,鉴于 go 块使用的线程池有限,它通常被宣传以避免它 - 如果你在 go 块中进行阻塞 I/O,则线程可能会长时间阻塞,而无法执行纯计算。

您应该考虑使用一个完全独立的线程池(例如 ExecutorService)或至少 clojure.core.async/thread。

一些资源
https://martintrojer.github.io/clojure/2013/07/07/coreasync-and-blocking-io
https://eli.thegreenplace.net/2017/clojure-concurrency-and-blocking-with-coreasync/

...