Coffee Break Clojure, Async

Coffee Break Clojure, Async

💡 原文中文,约13000字,阅读约需31分钟。
📝

内容提要

文章讲解Clojure异步编程与并发控制:区分同步、异步、并发、并行;介绍future/deref、agent/send、pmap等机制,并对比core.async的go与chan,借鉴Go的CSP模型,用channel解耦状态管理、控制并发,最后建议按场景权衡选择。

🔎

延伸解读

异步与并发的本质区别

文章用工厂流水线和桌游店占位的例子,清晰区分了同步、异步、并发和并行。异步是局部状态不同步,对系统整体影响小;并发则是宏观的执行效率问题,涉及多个任务同时推进。并行强调物理上同时运行在多核,而并发是组织程序的方式。理解这些概念有助于在编程中正确选择抽象,避免混淆。

Clojure 异步工具的选择逻辑

future/deref 轻量易用,类似 JavaScript 的 async/await,适合简单异步任务;agent/send 关注数据处理且线程安全,适合管理共享状态;pmap 适合数量可控的并行处理;core.async 的 go 和 chan 借鉴 Go 的 CSP 模型,适合高并发复杂系统,通过 channel 解耦组件。文章强调没有万金油方案,需根据场景权衡。

core.async 的实用模式与限制

core.async 的 go block 和 channel 能实现类似 Go 的并发控制,例如用带缓冲的 channel 做信号量限制并发数,或用 alt! 实现 select 多路复用。但文章也指出其缺点:<! 和 >! 必须直接位于 go block 内,否则报错,需通过传参或宏规避。此外,channel 操作非函数式,需接受状态变更。

状态集中管理的架构启示

文章通过 Go 的 stateful goroutine 例子说明,将状态管理集中到一个组件,其他组件通过 channel 通信,可以解耦、方便测试且无需加锁。作者分享实际经验:用单一写者处理 SQLite 写入,避免锁竞争,但需注意程序意外退出时 channel 中未处理数据的丢失风险。这种模式在 Clojure 中可用 go-loop 和 alt! 实现。

❓

Q&A

Clojure 中 future 和 deref 是如何实现异步编程的?

future 宏接收表达式并立即返回一个 ref,表示未来可用的数据。deref 用于解引用获取值,如果值未就绪则阻塞当前线程。例如,将耗时的 HTTP 请求包裹在 future 中,可以继续处理其他任务,之后用 deref 获取结果。deref 支持超时控制:(deref ref timeout fallback)。

Clojure 的 agent 和 send 适合什么场景?

agent 适合管理异步状态更新,send 用于向 agent 发送函数以更新其保存的值。agent 是线程安全的,无需手动加锁。例如,可以用 agent 实现计数器或并发爬虫,通过 send 累积结果。但 agent 只能发送函数,不能直接替换值,用途有限。

core.async 中的 go 和 chan 与 Go 语言有什么相似之处?

core.async 借鉴了 Go 的 CSP 模型,go 块用于异步执行语句,chan 是消息队列,支持 >! 和 <! 操作进行放取。go 返回一个 channel,chan 可设置缓冲区大小以控制阻塞。这种风格与 Go 的 goroutine 和 channel 类似,用于解耦组件和管理并发。

在 Clojure 中如何用 channel 实现并发控制?

可以用 chan 实现信号量:先向 channel 放入固定数量的信号(如 50 个空 map),派发任务前从 channel 取信号,取不到则阻塞,任务完成后放回信号。这样能限制同时运行的 go 线程数量。也可以使用带缓冲的 channel,放满后阻塞。

pmap 和 map 有什么区别?什么时候该用 pmap?

pmap 是并行版本的 map,底层使用 future,能并发处理数据。对于耗时任务,pmap 比 map 快很多。例如,处理 4 个耗时 3 秒的任务,map 需要约 12 秒,pmap 只需约 3 秒。pmap 返回半惰性序列,适合处理数量可控且相对轻量的数据。

Clojure 中 future、agent、core.async 应该如何选择?

future/deref 轻量易用,类似 JavaScript 的 async/await,适合简单异步任务;agent/send 关注数据处理,线程安全,适合管理状态;pmap 适合数量可控的并行数据处理;core.async 的 go/chan 适合高并发复杂系统,能解耦组件、控制并发,但牺牲函数式风格。应根据实际场景权衡。

🏷️

标签

➡️

继续阅读