第3章:函数式并发(Functional Concurrency)
一、导读
1.1 本章学习目标
第3章介绍函数式编程在并发中的应用。函数式编程的核心——不可变性——天然适合并发。如果数据不可变,就不需要锁。
本章的核心学习目标包括:
- 理解不可变性与并发的关系
- 掌握 Clojure 的原子(Atom)、代理(Agent)、引用(Ref)
- 理解函数式并发如何避免数据竞争
1.2 为什么本章如此重要
函数式并发模型证明了:通过限制可变状态,可以大幅简化并发编程。不可变数据结构、纯函数、值语义——这些函数式编程的核心概念,在并发场景中展现出巨大的优势。
1.3 核心问题引导
为什么不可变性对并发如此重要?
Clojure 的 Atom、Agent、Ref 分别适用于什么场景?
函数式并发模型能替代线程与锁吗?
持久化数据结构如何支持高效的"修改"?
二、核心概念详解
2.1 不可变性与并发
clojure
;; 不可变数据:无需锁
(def data {:name "Alice" :age 30})
;; "修改"返回新结构,原结构不变
(def updated (assoc data :age 31))
;; 多线程安全:因为数据不可变
(future (println (:name data))) ;; 安全
(future (println (:age data))) ;; 安全2.2 Atom(原子操作)
clojure
;; Atom:同步的、独立的状态变更
(def counter (atom 0))
;; swap! 使用函数原子性地更新
(swap! counter inc)
(swap! counter + 5)
(deref counter) ;; => 6
@counter ;; => 6
;; compare-and-set
(compare-and-set! counter 6 10)2.3 Agent(异步操作)
clojure
;; Agent:异步的、独立的状态变更
(def logger (agent []))
;; send 异步发送操作
(send logger conj "Log entry 1")
(send logger conj "Log entry 2")
;; 等待完成
(await logger)
@logger ;; => ["Log entry 1" "Log entry 2"]2.4 Ref(事务性操作)
clojure
;; Ref:同步的、协调的状态变更
(def account-a (ref 1000))
(def account-b (ref 500))
;; dosync 保证事务性
(dosync
(alter account-a - 200)
(alter account-b + 200))
;; 如果事务中的任何一步失败,整个事务回滚三、实战案例
3.1 案例一:银行账户系统
clojure
(def accounts (ref {}))
(defn create-account [id balance]
(dosync
(alter accounts assoc id (ref balance))))
(defn transfer [from-id to-id amount]
(dosync
(let [from (get @accounts from-id)
to (get @accounts to-id)]
(when (and from to (>= @from amount))
(alter from - amount)
(alter to + amount)
true))))
(create-account :alice 1000)
(create-account :bob 500)
;; 并发转账
(future (transfer :alice :bob 100))
(future (transfer :bob :alice 50))四、本章小结
不可变性:消除了数据竞争的根本原因。
Atom:同步、独立的原子操作。
Agent:异步、独立的操作。
Ref:同步、协调的事务操作。
五、思考与练习
不可变数据结构的内存开销有多大?持久化数据结构如何解决?
STM 和数据库事务有什么相似之处?
函数式并发模型在什么场景下比线程与锁更合适?