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-userregister-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块的线程池有限,它经常被反对这样做——如果在你使用的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/

...