Haskell的軟件事務(wù)內(nèi)存(STM)

傳統(tǒng)的并發(fā)變成模型通過(guò)Mutex/Conditional Variable/Semaphore的設(shè)施來(lái)控制對(duì)共享資源的訪問(wèn)控制,但是這一經(jīng)典模型使得編寫(xiě)正確高效的并發(fā)程序變得異常困難:

  1. 遺漏合適的鎖保護(hù)導(dǎo)致的race condition
  2. 鎖使用不當(dāng)導(dǎo)致的死鎖deadlock
  3. 異常未合適處理導(dǎo)致的程序崩潰
  4. 條件變量通知操作遺漏導(dǎo)致的等待處理沒(méi)有被合適的喚醒
  5. 鎖粒度控制不當(dāng)造成性能下降

STM(Software Transaction Memory)提供了一種簡(jiǎn)潔而又安全的方式來(lái)嘗試完美地解決上述大部分問(wèn)題。

基本思想

STM的基本設(shè)計(jì)規(guī)則如下:

  • 對(duì)共享資源的訪問(wèn)進(jìn)行控制從而使不同線程的操作相互隔離
  • 規(guī)則約束:
    • 如果沒(méi)有其它線程訪問(wèn)共享數(shù)據(jù),那么當(dāng)前線程對(duì)數(shù)據(jù)的修改同時(shí)對(duì)其它線程可見(jiàn)
    • 反之,當(dāng)前線程的操作將被完全丟棄并自動(dòng)重啟

這里的要么全做要么什么也不做的方式保證了共享數(shù)據(jù)訪問(wèn)操作的原子性,和數(shù)據(jù)庫(kù)中的Transaction很相像。

Haskell定義

模塊和類(lèi)型

GHC的支持在Control.Concurrent.STM中,并提供了TVar(相對(duì)于MVar):

newtype STM a 
    = GHC.Conc.Sync.STM (GHC.Prim.State# GHC.Prim.RealWorld
                         -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))

-- STM is an instance of Monad and Functor
instance Monad STM;
instance Functor STM;

--TVar type wraps a data of abstract type a
data TVar a;

--creation functions
newTVar :: a -> STM (TVar a)

--readTVar
readTVar::Tvar a -> STM a
--writeTVar
writeTVar::TVar a -> a -> STM ()

-- atomically provide wrapper to convert STM types to plain IO type
atomically :: STM a -> IO a

這里STM提供了一個(gè)STM類(lèi)型的抽象,并且定義其自身為MonadFunctor的實(shí)例。TVar則提供了對(duì)數(shù)據(jù)類(lèi)型的封裝和Monadic操作。

一個(gè)簡(jiǎn)單的例子

下邊是一個(gè)基本的例子:

module Main where
import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM
 
main = do shared <- atomically $ newTVar 0
          before <- atomRead shared
          putStrLn $ "Before: " ++ show before
          forkIO $ 25 `timesDo` (dispVar shared >> milliSleep 20)
          forkIO $ 10 `timesDo` (appV ((+) 2) shared >> milliSleep 50)
          forkIO $ 20 `timesDo` (appV pred shared >> milliSleep 25)
          milliSleep 800
          after <- atomRead shared
          putStrLn $ "After: " ++ show after
 where timesDo = replicateM_
       milliSleep = threadDelay . (*) 1000
 
atomRead = atomically . readTVar
dispVar x = atomRead x >>= print
appV fn x = atomically $ readTVar x >>= writeTVar x . fn

這里創(chuàng)建了一個(gè)初始為0的共享變量,并且啟動(dòng)三個(gè)線程分別做不同的操作:

  • 第一個(gè)線程每隔20毫秒打印當(dāng)前的變量
  • 第二個(gè)線程每隔50毫秒將變量當(dāng)前值倍2
  • 第三個(gè)線程每隔25毫秒取出當(dāng)前變量的值并將其減1
  • 主線程等待800毫秒(每個(gè)子線程執(zhí)行500毫秒)打印共享變量的數(shù)值

這個(gè)例子可以看出STM使代碼變得相當(dāng)簡(jiǎn)潔優(yōu)雅。

Retry

Haskell的STM API提供了retry機(jī)制,當(dāng)某個(gè)transaction不能成功的時(shí)候,retry可以重新啟動(dòng)整個(gè)Transaction,當(dāng)然這個(gè)Transaction只有當(dāng)其它線程對(duì)共享數(shù)據(jù)做修改之后才會(huì)重新啟動(dòng),從而避免性能損失。
下邊是一個(gè)例子:

transfer :: Gold -> Balance -> Balance -> STM ()

transfer qty fromBal toBal = do
  fromQty <- readTVar fromBal
  when (qty > fromQty) $
        retry
  writeTVar fromBal (fromQty - qty)
  readTVar toBal >>= writeTVar toBal . (qty +)

參考

  1. STM in Real World Haskell, chapter 28
  2. STM:Wikipedia
  3. Haskell wiki on STM
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容