Haskellで並行処理

forkIOでスレッドを起動できる。forkIOは(IOモナドに包まれた)スレッドIDを返すので、取っておいて後でkillThreadするとThreadKilled例外を投げて終了できる。

import Control.Concurrent

main :: IO ()
main = do
  id <- forkIO $ subThread 0
  threadDelay 5000000
  killThread id

subThread :: Int -> IO ()
subThread num = do
  putStrLn $ "loop " ++ (show num)
  threadDelay 1000000
  subThread $ num + 1

スレッド間でメッセージを送受信したい場合は、MVarを使う。MVarは容量1のメッセージボックスで、既にメッセージが入ってる時に更に書き込もうとするとブロックするし、逆にメッセージが無い時に読み込もうとしてもブロックする。ChanはMVarの容量無制限バージョンで、書き込みはブロックしない。

import Control.Concurrent

main :: IO ()
main = do
  numMVar <- newEmptyMVar
  id <- forkIO $ subThread numMVar
  threadDelay 1000000
  putMVar numMVar 0
  threadDelay 4000000
  currentNum <- takeMVar numMVar --空の場合はブロック
  putStrLn $ "numMVar: " ++ (show currentNum)
  killThread id

--subThread :: f Int -> IO ()
subThread numMVar = do
  currentNum <- takeMVar numMVar --上のputMVar numMVar 0が来るまでは空なのでブロック
  putStrLn $ "loop " ++ (show currentNum)
  threadDelay 1000000
  putMVar numMVar $ currentNum + 1
  subThread $ numMVar

スレッド間で変数を共有したい場合は、IORefを使ってatomicModifyIORefで更新する。atomicModifyIORefには「IORef型の変数」と「IORefが持つ値を受け取って(更新後の値, atomicModifyIORefの戻り値にしたい値)を返す関数」を渡す。

import Control.Concurrent
import Data.IORef

main :: IO ()
main = do
  numRef <- newIORef 0
  id <- forkIO $ subThread numRef
  threadDelay 1000000
  _ <- atomicModifyIORef numRef (\x -> (x+10, x))
  threadDelay 4000000
  currentNum <- readIORef numRef
  putStrLn $ "numRef: " ++ (show currentNum)
  killThread id

subThread :: IORef Int -> IO ()
subThread numRef = do
  currentNum <- atomicModifyIORef numRef (\x -> (x+1, x))
  putStrLn $ "loop " ++ (show currentNum)
  threadDelay 1000000
  subThread $ numRef

IORefの場合アトミックにできるのはatomicModifyIORefに渡した関数だけなので、複数のIORefを扱いたい時はSTMを使うといいらしい……けどややこしいので略。