LoginSignup
6
2

More than 3 years have passed since last update.

MVarとTVar

Last updated at Posted at 2018-05-21

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の順番が決まっている。

読むだけのスレッドと、書くだけのスレッドで、順番に動作することが保障されてる時や、
単一スレッドで、ループ中に値を変化させながら使う時に使える。はず。

こんな感じで使える。

MVar.hs
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

こんな感じで使える。

TVar.hs
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.PNG

また、TVarの中身を取り出して、それを変更する関数も用意されている。
modifyTVar :: TVar a -> (a -> a) -> STM ()
(modifyTVarは、non-strict。strict版のmodifyTVar'もある。)

こんな感じで使える。

TVar_modify.hs
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

出力はこんな感じになる。
TVar_modify.PNG

使用時注意

中身は遅延評価されるので、注意

下みたいなことをすると、おそらく思ってた値と違う値になるので注意。

TVar_lazy.hs
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]"

が出力されそうだが、実際の出力は、こんな感じになる。
Tvar_lazy.png

期待の出力にするには、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する必要ないですが...)

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2