傳統(tǒng)的并發(fā)變成模型通過(guò)Mutex/Conditional Variable/Semaphore的設(shè)施來(lái)控制對(duì)共享資源的訪問(wèn)控制,但是這一經(jīng)典模型使得編寫(xiě)正確高效的并發(fā)程序變得異常困難:
- 遺漏合適的鎖保護(hù)導(dǎo)致的race condition
- 鎖使用不當(dāng)導(dǎo)致的死鎖deadlock
- 異常未合適處理導(dǎo)致的程序崩潰
- 條件變量通知操作遺漏導(dǎo)致的等待處理沒(méi)有被合適的喚醒
- 鎖粒度控制不當(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)型的抽象,并且定義其自身為Monad和Functor的實(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 +)