Control.Concurrent.STM

STMってのはSoftware Transactional Memoryの頭文字。

基本はTVar aに対し、読み書きする機能。ほぼData.IORefと同じようにnew/read/writeして使う。

  • 対象: IORef → TVar
  • 処理: IO → STM

ただし、機能はSTM(モナド)型であるため、Threadで使うIOにするために、atomicallyでくくることになる。その部分は排他的に処理が行われる。


以下は単純に整数を読み書きする例

import Control.Concurrent
import Control.Concurrent.STM

main
    = do var <- atomically (newTVar 0)
         forkIO (myproc var "p1")
         forkIO (myproc var "p2")
         getLine
         return ()

myproc :: TVar Int -> String -> IO ()
myproc var name
    = do v <- atomically (readWrite var)
         putStrLn (name ++ ": " ++ v)
         yield -- 基本はプリエンティブのようなので切り替え
         myproc var name -- 無限ループ
    where
      readWrite :: TVar Int -> STM String
      readWrite var 
          = do v <- readTVar var
               let newV = v + 1
               writeTVar var newV
               return $ show newV

排他的にインクリメントしてるので、同じ数は二つ出力されない。


STMの特徴はSTM処理の中でretryすることができる。TVar変数が条件に合わないとき(たとえば、空リストのとき、とか)、retryすると再び処理が走る。


以下、リストを使った消費者生産者処理。

main
    = do var <- atomically (newTVar [])
         forkIO $ produce var
         forkIO $ consume var
         getLine
         return ()

produce var
    = prod var 0
    where
      prod var n
          = do atomically (post var n)
               yield
               prod var (n + 1)
      post var n
          = do l <- readTVar var
               let newL = l ++ [n]
               writeTVar var newL
               return ()

consume var
    = do v <- atomically (pop var)
         putStrLn $ "consume: " ++ (show v)
         consume var
    where
      pop var
          = do l <- readTVar var
               case l of
                 [] -> retry -- 空なのでリトライ
                 (h:t)
                     -> do writeTVar var t
                           return h

retryされたとき、別の処理を走らせるにはSTM処理をorElseでつなぐことができる。どんな例があるだろうか。