引用类型Ref #

一、Ref简介 #

Ref是Clojure的软件事务内存(STM)系统核心,用于管理多个需要协调变更的状态。Ref提供ACI事务特性。

1.1 特点 #

  • 协调式状态管理
  • 软件事务内存(STM)
  • ACI特性(原子性、一致性、隔离性)
  • 适合多变量协调

1.2 创建Ref #

clojure
(def account-a (ref 1000))
(def account-b (ref 500))

@account-a

(deref account-b)

二、事务基础 #

2.1 dosync #

所有Ref操作必须在事务中进行:

clojure
(def account-a (ref 1000))
(def account-b (ref 500))

(dosync
  (alter account-a - 100)
  (alter account-b + 100))

[@account-a @account-b]

2.2 alter #

基于当前值更新:

clojure
(def counter (ref 0))

(dosync
  (alter counter inc))

@counter

(dosync
  (alter counter + 10))

@counter

2.3 ref-set #

直接设置新值:

clojure
(def state (ref :idle))

(dosync
  (ref-set state :running))

@state

2.4 commute #

用于可交换的操作,性能更好:

clojure
(def counter (ref 0))

(dosync
  (commute counter inc))

@counter

(dosync
  (commute counter + 5))

@counter

三、转账示例 #

3.1 基本转账 #

clojure
(def account-a (ref 1000))
(def account-b (ref 500))

(defn transfer [from to amount]
  (dosync
    (alter from - amount)
    (alter to + amount)))

(transfer account-a account-b 200)

[@account-a @account-b]

3.2 带验证的转账 #

clojure
(defn transfer-safe [from to amount]
  (dosync
    (let [balance @from]
      (if (>= balance amount)
        (do
          (alter from - amount)
          (alter to + amount)
          {:success true})
        {:success false :error "Insufficient funds"}))))

(transfer-safe account-a account-b 100)

(transfer-safe account-a account-b 10000)

3.3 多账户操作 #

clojure
(def accounts
  {:a (ref 1000)
   :b (ref 500)
   :c (ref 200)})

(defn multi-transfer [transfers]
  (dosync
    (doseq [[from to amount] transfers]
      (alter (accounts from) - amount)
      (alter (accounts to) + amount))))

(multi-transfer [[:a :b 100] [:b :c 50]])

(map (fn [[k v]] [k @v]) accounts)

四、事务特性 #

4.1 原子性 #

事务要么全部成功,要么全部回滚:

clojure
(def x (ref 0))
(def y (ref 0))

(try
  (dosync
    (alter x inc)
    (throw (ex-info "Error!" {}))
    (alter y inc))
  (catch Exception e
    (println "Caught:" (.getMessage e))))

[@x @y]

4.2 一致性 #

事务中的读取看到一致的状态:

clojure
(def a (ref 0))
(def b (ref 0))

(defn consistent-read []
  (dosync
    [@a @b]))

(defn update-both []
  (dosync
    (alter a inc)
    (alter b inc)))

(future
  (dotimes [_ 1000]
    (update-both)))

(Thread/sleep 100)

(consistent-read)

4.3 隔离性 #

并发事务相互隔离:

clojure
(def counter (ref 0))

(defn increment []
  (dosync
    (let [current @counter]
      (Thread/sleep 10)
      (ref-set counter (inc current)))))

(def futures
  (doall
    (for [_ (range 10)]
      (future (increment)))))

(doseq [f futures] @f)

@counter

五、事务函数 #

5.1 ensure #

防止其他事务修改:

clojure
(def a (ref 0))
(def b (ref 0))

(defn update-with-ensure []
  (dosync
    (ensure a)
    (alter b + @a)))

(defn update-a []
  (dosync
    (alter a inc)))

5.2 io! #

标记IO操作,禁止在事务中执行:

clojure
(defn do-io []
  (io!
    (println "This is IO")))

(def state (ref 0))

(try
  (dosync
    (do-io)
    (alter state inc))
  (catch Exception e
    (println "Caught:" (.getMessage e))))

六、嵌套事务 #

6.1 嵌套dosync #

clojure
(def x (ref 0))

(defn inner-transaction []
  (dosync
    (alter x + 10)))

(defn outer-transaction []
  (dosync
    (alter x inc)
    (inner-transaction)))

(outer-transaction)

@x

嵌套事务合并为一个事务。

6.2 事务可见性 #

clojure
(def a (ref 0))

(defn nested-update []
  (dosync
    (alter a inc)
    (dosync
      (alter a inc))))

(nested-update)

@a

七、实践示例 #

7.1 银行系统 #

clojure
(defrecord Account [id owner balance])

(defn make-account [id owner balance]
  (ref (->Account id owner balance)))

(defn get-balance [account]
  (:balance @account))

(defn deposit [account amount]
  (dosync
    (alter account update :balance + amount)))

(defn withdraw [account amount]
  (dosync
    (let [balance (:balance @account)]
      (if (>= balance amount)
        (do
          (alter account update :balance - amount)
          {:success true})
        {:success false :error "Insufficient funds"}))))

(defn transfer-funds [from to amount]
  (dosync
    (let [result (withdraw from amount)]
      (if (:success result)
        (do
          (deposit to amount)
          {:success true})
        result))))

(def acc1 (make-account 1 "Alice" 1000))
(def acc2 (make-account 2 "Bob" 500))

(transfer-funds acc1 acc2 200)

[(get-balance acc1) (get-balance acc2)]

7.2 库存管理 #

clojure
(def inventory (ref {:apple 100 :banana 50 :orange 75}))

(defn check-stock [item]
  (get @inventory item 0))

(defn reserve [item quantity]
  (dosync
    (let [current (get @inventory item 0)]
      (if (>= current quantity)
        (do
          (alter inventory update item - quantity)
          {:success true :reserved quantity})
        {:success false :available current}))))

(defn restock [item quantity]
  (dosync
    (alter inventory update item (fnil + 0) quantity)))

(reserve :apple 30)
(check-stock :apple)

(restock :banana 20)
(check-stock :banana)

7.3 游戏状态 #

clojure
(def game-state
  (ref {:players {}
        :enemies []
        :items []}))

(defn add-player [id name]
  (dosync
    (alter game-state
           assoc-in [:players id]
           {:name name :health 100 :position [0 0]})))

(defn move-player [id new-pos]
  (dosync
    (alter game-state
           assoc-in [:players id :position] new-pos)))

(defn damage-player [id amount]
  (dosync
    (alter game-state
           update-in [:players id :health]
           (fn [h] (max 0 (- h amount))))))

(add-player 1 "Alice")
(move-player 1 [10 20])
(damage-player 1 30)

@game-state

八、性能考虑 #

8.1 事务大小 #

clojure
(defn small-transaction []
  (dosync
    (alter some-ref inc)))

(defn large-transaction []
  (dosync
    (doseq [r many-refs]
      (alter r inc))))

保持事务尽可能小。

8.2 alter vs commute #

clojure
(def counter (ref 0))

(dosync (alter counter inc))

(dosync (commute counter inc))

commute 在低冲突时性能更好,但语义不同。

8.3 避免IO #

clojure
(defn bad-transaction []
  (dosync
    (let [data @some-ref]
      (write-to-db data)
      (alter other-ref inc))))

IO操作不应在事务中执行。

九、Ref vs Atom #

特性 Ref Atom
协调 多变量 单变量
事务 支持 不支持
性能 较低 较高
复杂度

十、总结 #

Ref操作速查:

操作 说明
ref 创建Ref
dosync 创建事务
alter 更新值
ref-set 设置值
commute 可交换更新
ensure 保护读取

关键点:

  1. Ref用于协调多变量状态
  2. 所有操作必须在 dosync
  3. STM提供ACI特性
  4. 避免在事务中执行IO
  5. 保持事务小而快

下一步,让我们学习代理类型Agent!

最后更新:2026-03-27