2024 状态 Clojure 问卷调查! 中分享您的想法。

欢迎!请查看 关于 页面以了解更多关于此运作方式的信息。

0
IO

一般来说,我们想避免阻塞 I/O 并使其挂起。让我们假设我们通过一个 go 块使保存到数据库的操作非阻塞。

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

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

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

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

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

再次,send-welcome-email 可以在其他函数(比如 last-fn)中使用,然后我们还需要另一个 go 块。所以我们在使用 go 创建了一连串的函数,就像最初的那个一样。

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

我的问题是:这是使用 go 的可接受/正确方式吗?或者我的问题错了。

我担心,因为看起来为了执行一个函数,我们可能需要创建几个 go(及然后的通道)并对其进行垃圾收集。这看起来有些不够优雅。我对 core.async 并不完全了解,但相信 go 是为了使 I/O 非阻塞。另一方面,如果我们把 go 当作状态机,那么我们似乎应该避免在 go 中进行 I/O 和使内部操作无需副作用。

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 块中进行阻塞 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/

...