soft transactional memory
Table of Contents
1 Software Transactional Memory
原文 編寫併發軟件很困難,由於運行順序無法控制,產生的錯誤很難復現,同時,全面的測試也需要花費更多的精力。 多線程軟件編寫通常有兩種劃分方法,
- 一種方法是把單一的任務劃分爲多個獨立的小片段,然後每個線程獨立執行各個片段;
- 另一種方法是協調多個任務同步執行;
顯然,後一種方法更難實現,而且不容易追蹤和差錯。
編寫併發軟件有許多種模型。現在最長用的是:locks, actors, transaction memory。這篇文章簡單介紹了三種模型,然後會深入探討soft-base Transactional Memory 本文的目的有:
- 介紹STM
- STM的性能特點給出證明
- 推薦多種語言對STM模型的支持
- 推薦對STM感興趣的開發者實現Clojure的STM模型
- 提供開發者對Clojure中的STM模型足夠的認識,讓他們開發出實用的STM開發測試工具,如跟蹤事務處理次數等。
1.1 Lock-base Concurrency
鎖機制的併發任務,一般多線程訪問共享內存。最簡單的模型爲:在訪問共享內存代碼執行前,首先要獲取互斥鎖,保證同一時間只有一個線程訪問這段共享內存。 優勢:
- 開發者可以顯示的控制鎖的行爲
- 許多開發者都很熟悉這一模型
- 許多開發語言內置支持此模型
劣勢:
- 很難控制什麼時候需要用鎖
- 鎖使用異常情況下, 互斥鎖變量能夠被訪問
- 死鎖
- 錯誤狀態恢復比較複雜,開發者需要記住釋放互斥鎖
- 正常的狀態同步方法會組合會成爲複雜的方法,但是卻需要額外的互斥鎖
- 悲觀鎖。把共享區的訪問建立在同時訪問只有一個線程的基礎上,這並不總是正確的,而且這一假設降低了併發的效率。
1.2 Actor-based Concurrency
Actor Mode機制是另一個併發模式訪問共享內存的解決方法。把Actor視爲一個實體,即一個獨立的進程或者輕量級的線程。 actor不會主動訪問共享內存區域,而是被動的接收異步消息。當一個actor接收到消息後,可以做以下幾種操作:
- 新建actor
- 發送消息給其他actor
- 定義如何處理下一個接收到的消息
Actor-based concurrency的優勢:
- 由於actors不會訪問共享區域,共享區的訪問不再需要同步
劣勢:
- 由於actor不會直接訪問共享內存,通過發送消息共享,消息內容會很大
- 不同的actor持有狀態不同
Actor mode被Erlang, Haskell, Scala所支持
1.3 Transactional Memory
Transactional Memory是另一個併發模式訪問共享的解決方法。這種方法簡化了開發併發軟件的複雜度。 Transactional Memory的概念類似於DB Transactions,提供了ACID幾種特性:
- "A" is for atomic,指所做的操作完全成功,或者完全不成功。
- "C" is for consistant,指數據在Transaction期間,從開始到結束都是一致的,不會中途修改。
- "I" is for isolated,指在Transaction期間修改的數據不會被其他進程所見,只有commit後纔會。
- "D" is for durable,指一旦commit後,所做修改的數據不會丟失,即使在軟硬件出錯的情況下。
Transactions根據代碼不同有許多不同的實現,在其他不同的線程視角,在做commit操作後,所有的共享內存修改都是同時生效的。但是在 commit之前,其他線程並不能看到所做的修改。這是因爲所有的操作都是在一塊獨立的鏡像區域操作的。例如:Transaction A對一塊 內存區域做了修改,同時Transaction B也在對這塊內存做修改,並且B在A之前做了commit操作,這是A會用B所做修改後的數據重新做之前的 修改操作。這些操作特性保證了Transactional Memory的Atomic,Consistant 和Isolated。注意Transational Memory並不能保證durable 的特性,因爲軟件緩存異常或者硬件異常,都會引起數據的丟失。 Transactional Memory是樂觀的,所有它所修改的數據,它都認爲其他線程沒有修改過,如果被其他線程修改過,那麼它會重新讀取最新的數據, 並在新數據基礎上做修改操作。這些特性保證了所有的transaction操作不會產生不可恢復等副作用。 優勢:
- 可以增加併發運行的效率
- 編寫併發軟件更簡單
- 實現可以保證不會發生deadlock、livelock、race condition的發生
劣勢:
- 存在許多潛在的transaction retries,這會耗費許多資源
- 需要緩存許多transaction中間狀態值
- 分析工具匱乏
1.4 Clojure有4種引用類型:
- Var,Var擁有和其他線程共享的原值和線程獨有的值。def,set!,binding可以用來修改Var變量值。一般Var變量都是建議用作常量。一種比較 常用的修改Var變量值的是當Var變量作爲配置參數時。
- Atom,Atom在所有線程中擁有唯一的值。所以對它的訪問都是需要原子性。reset!,compare-and-set!,swap!函數可以用來操作Atom變量。
- Agent, Agents在所有線程中都是相同的值。修改Agents變量值是通過調用異步函數實現的。發送給相同Agent的動作會排在同一隊列中, 所以同一時間只有一個Agent動作在執行。send和send-off是用來操作Agent變量的,當動作異步執行後,立即會有返回值。這兩個操作函數 唯一的區別是send是固定Queue大小的,而send-off的Queue大小是變化的。await函數會終止當前線程的操作,直到發送的操作全部發送到 指定的Agent;await-for函數唯一的區別是可以設置timeout。Agent的修改動作都是在Transaction內部進行的,只有當commit完成後 纔會返回。這一特點可以用來執行有副作用的transaction,像IO操作等。
- Ref,Ref在所有線程中擁有相同的值。所有修改操作都在STM Transaction中進行。ref-set,alter,commute幾個是操作函數。