2つのVar
2つのVarがある。
それぞれ、(自分の中では!)以下の感じ
MVar : R/Wの順番が決まっている。デッドロック注意。
TVar : R/Wをランダムにできる。中身に注意。
MVar
空で初期化は、newEmptyMVar :: IO (MVar a)
値で初期化は、newMVar :: a -> IO (MVar a)
書き込みは、putMVar :: MVar a -> a -> IO ()
読み出しは、takeMVar :: MVar a -> IO a
MVarの中身が空の場合のみputでき、MVarの中身がある場合のみtakeできる。
それぞれ、動作できる状態でない場合、動作できるまで、wait(?)する。
つまり、
put -> take -> put -> take -> ..の繰り返しのみ
R/Wの順番が決まっている。
読むだけのスレッドと、書くだけのスレッドで、順番に動作することが保障されてる時や、
単一スレッドで、ループ中に値を変化させながら使う時に使える。はず。
こんな感じで使える。
import Control.Concurrent
main :: IO ()
main = do
mVar <- newEmptyMVar
forkIO $ putLoop mVar 5
takeLoop mVar 5
return ()
putLoop :: MVar Int -> Int -> IO ()
putLoop _ 0 = return ()
putLoop mVar i = do
putMVar mVar i
putLoop mVar $ i - 1
takeLoop :: MVar Int -> Int -> IO ()
takeLoop _ 0 = return ()
takeLoop mVar i = do
a <- takeMVar mVar
takeLoop mVar $ i - 1
ちなみに、上記ソースで、
putLoopのloop数 < takeLoopのloop数
としたり、
putLoopとtakeLoopを入れ替えて、takeLoopのloop数 < putLoopのloop数
として実行すると、
MVar.hs: thread blocked indefinitely in an MVar operation
というエラーが発生する。
ただし、実行後すぐに発生はせず、可能なところまで動作し、解除されないMVarのwait(?)に入ってからエラーが発生する。
(上記ソースで、putLoopのloop数を5、takeLoopのloop数を6とした場合、
5回分のputMVar,takeMVarまでは、正常に実行され、6回目のtakeMVarでエラーが出る。)
また、
MVarには、
readMVar :: MVar a -> IO a
というのもある。
これは、MVarの内容を取得するが、MVarを空にしない。
readMVar は、中で、takeMVarしてからputMVarしてるらしい。
ただし、もちろん、MVarの中身が空の時は、waitする。
上記のputMVar, takeMVar, readMVarのnon-blocking版(=waitしない版)もあり、それぞれ
tryPutMVar :: MVar a -> IO Bool
tryTakeMVar :: MVar a -> IO (Maybe a)
tryReadMVar :: MVar a -> IO (Maybe a)
tryPutMVarは、put出来たかの成否、
tryTakeMVar,tryReadMVarは、取れたらJustの値、取れなければNothing
が返る。
TVar
TransactionalなVar。
複数スレッドからランダムにR/Wを行う場合に使える。(はず。)
基本的に、STMモナドなので注意。(IOモナドで返す関数もいくつかある。)
空で初期化は無さそう。
値で初期化は、newTVar :: a -> STM (TVar a)
書き込みは、writeTVar :: TVar a -> a -> STM ()
読み出しは、readTVar :: TVar a -> STM a
こんな感じで使える。
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad.IO.Class
import Control.Monad.STM
main :: IO ()
main = do
tVar <- atomically $ newTVar 0
forkIO $ putLoop tVar 5
takeLoop tVar 5
return ()
putLoop :: TVar Int -> Int -> IO ()
putLoop _ 0 = return ()
putLoop tVar i = do
threadDelay (4 * 100 * 1000)
atomically $ writeTVar tVar i
print $ "writeTVar(" ++ show i ++ ") : " ++ show i
putLoop tVar $ i - 1
takeLoop :: TVar Int -> Int -> IO ()
takeLoop _ 0 = return ()
takeLoop tVar i = do
threadDelay (10 * 100 * 1000)
a <- atomically $ readTVar tVar
print $ "readTVar(" ++ show i ++ ") : " ++ show a
takeLoop tVar $ i - 1
これで、出力がこんな感じになる。
また、TVarの中身を取り出して、それを変更する関数も用意されている。
modifyTVar :: TVar a -> (a -> a) -> STM ()
(modifyTVarは、non-strict。strict版のmodifyTVar'もある。)
こんな感じで使える。
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad.IO.Class
import Control.Monad.STM
main :: IO ()
main = do
tVar <- atomically $ newTVar []
forkIO $ putLoop tVar 5
threadDelay (3 * 100 * 1000)
takeLoop tVar 5
return ()
putLoop :: TVar [Int] -> Int -> IO ()
putLoop _ 0 = return ()
putLoop tVar i = do
threadDelay (5 * 100 * 1000)
atomically $ modifyTVar tVar (\is -> i : is )
print $ "modifyTVar(" ++ show i ++ ") : Add " ++ show i
putLoop tVar $ i - 1
takeLoop :: TVar [Int] -> Int -> IO ()
takeLoop _ 0 = return ()
takeLoop tVar i = do
threadDelay (5 * 100 * 1000)
a <- atomically $ readTVar tVar
print $ "readTVar(" ++ show i ++ ") : " ++ show a
takeLoop tVar $ i - 1
使用時注意
中身は遅延評価されるので、注意
下みたいなことをすると、おそらく思ってた値と違う値になるので注意。
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad.IO.Class
import Control.Monad.STM
import System.IO.Unsafe (unsafePerformIO)
main :: IO ()
main = do
tVar <- atomically $ newTVar []
kVar <- atomically $ newTVar 0
putLoop tVar kVar 5
k <- atomically $ readTVar kVar
print $ "kVar : " ++ show k
t <- atomically $ readTVar tVar
print $ "tVar : " ++ show t
return ()
putLoop :: TVar [Int] -> TVar Int -> Int -> IO ()
putLoop _ _ 0 = return ()
putLoop tVar kVar i = do
atomically $ modifyTVar kVar (\k -> k + 1 )
atomically $ modifyTVar tVar (\ts -> (unsafePerformIO $ calc kVar) : ts )
print $ "modifyTVar(" ++ show i ++ ") : Add " ++ show (unsafePerformIO $ calc kVar)
putLoop tVar kVar $ i - 1
calc :: TVar Int -> IO Int
calc kVar = do
a <- atomically $ readTVar kVar
return $ a^2
上記ソースは
"tVar : [25,16,9,4,1]"
期待の出力にするには、putLoopでtVar入れる時に、下みたいに評価させてから入れればいい。
import Control.Exception
~省略~
putLoop tVar kVar i = do
atomically $ modifyTVar kVar (\k -> k + 1 )
t <- evaluate $ unsafePerformIO $ calc kVar
atomically $ modifyTVar tVar (\ts -> t : ts )
putLoop tVar kVar $ i - 1
(まぁ上に書いたソースだと、unsafePerformIO $ calc kVar
部分をletで拘束して、
それをmodifyTVarとprintで使用すれば、printの方で評価されるので、わざわざevaluateする必要ないですが...)