Clojure

參照和交易

雖然 變數 透過執行緒隔離確保安全使用可變儲存位置,但交易參照 (Refs) 透過 軟體交易記憶體 (STM) 系統確保安全共用使用可變儲存位置。Refs 在其生命週期內繫結到單一儲存位置,而且只允許在交易內對該位置進行變更。

如果你曾經使用過資料庫交易,那麼 Clojure 交易應該很容易理解 - 它們確保對 Refs 的所有動作都是原子性的、一致的和孤立的。原子性表示在交易中對 Refs 的每個變更都會發生或都不發生。一致性表示在允許交易提交之前,可以使用驗證器函數檢查每個新值。孤立性表示在交易執行期間,沒有任何交易會看到其他交易的影響。STM 的另一個常見功能是,如果交易在執行期間發生衝突,它會自動重試。

有許多方法可以執行 STM(鎖定/悲觀、無鎖/樂觀和混合),這仍然是一個研究問題。Clojure STM 使用 多版本並發控制,並使用適應性歷史佇列進行 快照隔離,並提供一個不同的 commute 操作。

在實務中,這表示

  1. 所有 Refs 的讀取都會看到交易起點(其「讀取點」)的「Ref 世界」一致快照。交易看到它所做的任何變更。這稱為交易中值

  2. 在交易期間對 Refs 所做的所有變更(透過 ref-setaltercommute)會顯示在「Ref 世界」時間軸中的單一時間點(其「寫入點」)發生。

  3. 此交易已ref-set / altered / ensured 的任何 Refs,不會有任何其他交易對其進行任何變更。

  4. 此交易已提交的任何 Refs,可能會有其他交易對其進行變更。這應該是沒問題的,因為commute 應用的函數應該是可交換的。

  5. 讀取器和提交器永遠不會阻擋寫入器、提交器或其他讀取器。

  6. 寫入器永遠不會阻擋提交器或讀取器。

  7. 交易中應避免 I/O 和其他具有副作用的活動,因為交易重試。io! 巨集可用於防止在交易中使用不純函數。

  8. 如果正在變更的 Ref 值的有效性限制取決於未變更的 Ref 的同時值,則可以透過呼叫 ensure 來保護第二個 Ref 不被修改。以這種方式「確保」的 Refs 將受到保護(項目 #3),但不會改變世界(項目 #2)。

  9. Clojure MVCC STM 設計用於與持續性集合搭配使用,強烈建議您使用 Clojure 集合作為 Ref 的值。由於 STM 交易中所做的所有工作都是推測性的,因此必須以低成本進行複製和修改。持續性集合具有免費的副本(只需使用原始副本,它無法被修改),而「修改」則有效率地共享結構。無論如何

  10. 放置在 Ref 中的值必須是,或被視為,不可變的!!否則,Clojure 無法為您提供協助。

範例

在此範例中,會建立一個包含對向量的參考的向量,每個向量都包含(最初是順序的)唯一數字。然後,會啟動一組執行緒,在交易中重複選擇兩個隨機向量中的兩個隨機位置並交換它們。除了使用交易之外,不會特別採取措施來防止不可避免的衝突。

(defn run [nvecs nitems nthreads niters]
  (let [vec-refs (vec (map (comp ref vec)
                           (partition nitems (range (* nvecs nitems)))))
        swap #(let [v1 (rand-int nvecs)
                    v2 (rand-int nvecs)
                    i1 (rand-int nitems)
                    i2 (rand-int nitems)]
                (dosync
                 (let [temp (nth @(vec-refs v1) i1)]
                   (alter (vec-refs v1) assoc i1 (nth @(vec-refs v2) i2))
                   (alter (vec-refs v2) assoc i2 temp))))
        report #(do
                 (prn (map deref vec-refs))
                 (println "Distinct:"
                          (count (distinct (apply concat (map deref vec-refs))))))]
    (report)
    (dorun (apply pcalls (repeat nthreads #(dotimes [_ niters] (swap)))))
    (report)))

執行時,我們會看到在洗牌過程中沒有任何值遺失或重複

(run 100 10 10 100000)

([0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19] ...
 [990 991 992 993 994 995 996 997 998 999])
Distinct: 1000

([382 318 466 963 619 22 21 273 45 596] [808 639 804 471 394 904 952 75 289 778] ...
 [484 216 622 139 651 592 379 228 242 355])
Distinct: 1000

建立 Ref:ref

檢查 Ref:deref (另請參閱 @ reader 巨集)

交易巨集:dosync io!

僅允許在交易中:ensure ref-set alter commute

Ref 驗證器:set-validator! get-validator