summaryrefslogtreecommitdiff
path: root/content/article
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-10-12 13:42:44 +0800
committereltrac <hi@eltr.ac>2026-10-12 13:42:44 +0800
commit356d48f3baf71ccb57a73d22651485aa4c6c63b4 (patch)
tree18adcf9b77c8e71d1bcb931c2e212a40810e832f /content/article
parentf05ee8acc0b26254e55d082ddc2211d84242aa58 (diff)
[draft] update
Diffstat (limited to 'content/article')
-rw-r--r--content/article/clj7.md46
1 files changed, 45 insertions, 1 deletions
diff --git a/content/article/clj7.md b/content/article/clj7.md
index 5d709615..500900ba 100644
--- a/content/article/clj7.md
+++ b/content/article/clj7.md
@@ -1,6 +1,6 @@
+++
title = 'Coffee Break Clojure, Async'
-subtitle = '异步编程和并发编程'
+subtitle = '异步编程和并发控制'
slug = ''
tags = []
categories = '茶歇蔻芥'
@@ -228,6 +228,50 @@ user=> (time (doall (pmap long-running-job (range 4))))
## Gopher?是你吗?
+命名空间 `clojure.core.async` 提供了另外一种异步编程的方式,但没有放在标准库里,必须要添加依赖才能使用。
+
+```clojure
+;; deps.edn
+org.clojure/core.async {:mvn/version "1.9.865"}
+;; leiningen
+[org.clojure/core.async "1.9.865"]
+```
+
+`core.async` 几乎和 Go 语言异步编程的要素一模一样,尤其是 `go` block——把语句放在 `go` block 里面,语句就会异步执行。把消息队列 `chan` 当作一等公民的做法,也如出一辙。根据 [Clojure 官方的博客文章](https://clojure.org/news/2013/06/28/clojure-clore-async-channels),这种异步编程风格其实一段历史了,Go 也是借鉴并现代化了这种做法。
+
+> The roots of this style go back at least as far as Hoare’s Communicating Sequential Processes (CSP), followed by realizations and extensions in e.g. occam, Java CSP and the Go programming language.
+
+无论如何,让我们来看看怎么用 `go` 和 `chan` 实现异步编程吧。如果你已经会写 Go,那么接下来的代码会很熟悉。
+
+```clojure
+(require '[clojure.core.async :refer [go chan >! <!]])
+
+(def queue (chan))
+
+(go
+ (<! queue "Hello?"))
+
+(go
+ (println (>! queue)))
+```
+
+`chan` 函数用于创建 *channel*(频道),可以理解为消息队列。对于 *channel*,有放(put)和拿(take)的操作,分别用 `>!`(放)和 `<!`(拿)表示。这里的 `!` 类似于 `?`,是 Clojure 的命名习惯,用于标识会对状态进行变更的操作。说到底,我们的确对状态进行了变更,上述语句缺乏引用透明性,不够函数式。
+
+`go` 在 Clojure 里是个宏,在 Go 里是个放在函数调用之前的关键字,效果都是让语句异步执行。值得注意的是,Clojure 中所有形式都是有返回值的,`go` 会返回一个 *channel*,`go` block 里的语句执行完成后,会把返回值通过 *channel* 传递回来。
+
+上述例子中,我们启动了两个 `go` 线程,一个线程把 `"Hello"` 放进 `queue` 里,另一个线程把 `"Hello"` 取出来。
+
+放和拿的操作可以阻断线程,因此可以用作**并发控制**。
+
+学过操作系统的读者可能知道信号量(semaphore)这个东西,我们可以用 `chan` 实现简单的信号量。比方说,我们先给一个 `chan` 存入 50 个信号,然后启动不定数量的 `go` 线程,但在启动线程之前,派发器必须要拿到信号量才行。如果信号量不够(尝试从空队列里取数据),就会阻塞派发器所在的线程,直到有信号量被回收。
+
+```clojure
+(def semaphore (chan))
+
+(dotimes [_ 50]
+ (>!! semaphore true))
+```
+
## Footnotes
[^1]: 不过其实也可以给 `agent` 发这样的函数:`(fn [_] "new value")`