Shared Transactional Memory
Haskell allows you to do imperative programming through its
shared memory transactional system. Shared memory variables are
created with newTVar(), read with readTVar(), and written with writeTVar().
Threads can be created with the forkIO() call.
You can wrap any combination of read/write/new inside an atomically()
call, and it will safely execute concurrently with other threads.
The atomically() call has another nice benefit, it converts an STM to
an IO. This allows us to print values obtained from the STM Monad.
Here's a small example of how to do some basic imperative programming
with Haskell.
stmtest.hs |
1 | import Control.Concurrent.STM |
2 | |
3 | pr x = do |
4 | y <- atomically(readTVar x) |
5 | putStrLn(show(y)) |
6 | |
7 | main = do |
8 | x <- atomically(newTVar 3) |
9 | pr x |
10 | atomically(writeTVar x 4) |
11 | pr x |
12 | atomically(writeTVar x 2) |
13 | pr x |
$ ghc --make stmtest.hs
[1 of 1] Compiling Main ( stmtest.hs, stmtest.o )
Linking stmtest ...
| $ ./stmtest
3
4
2
|
Building a Mutex with STM
The "retry" statement is key to making this code work. When it
is encountered, the transaction unrolls and waits for one of the
variables to change. When this happens it tries again.
mutex.hs |
1 | import Control.Concurrent |
2 | import Control.Concurrent.STM |
3 | |
4 | data Mutex = Locked | Unlocked |
5 | |
6 | newMutex = atomically(newTVar Unlocked) |
7 | |
8 | acquireMutex mut = atomically(do |
9 | val <- readTVar mut |
10 | case val of |
11 | Unlocked -> writeTVar mut Locked |
12 | Locked -> retry) |
13 | |
14 | releaseMutex mut = atomically(writeTVar mut Unlocked) |
15 | |
16 | threads mut counter n = doall [ |
17 | forkIO(do |
18 | acquireMutex mut |
19 | id <- myThreadId |
20 | atomically(do |
21 | val <- readTVar counter |
22 | writeTVar counter (val-1)) |
23 | putStrLn("I am thread "++show(id)) |
24 | releaseMutex mut) | i <- [1..n]] |
25 | |
26 | doall [] = putStr "" |
27 | doall (a:b) = do |
28 | a |
29 | doall b |
30 | |
31 | main = let |
32 | n = 10 |
33 | in |
34 | do |
35 | mut <- newMutex |
36 | counter <- atomically(newTVar n) |
37 | |
38 | -- start a group of n threads |
39 | threads mut counter n |
40 | |
41 | -- block until all threads finish |
42 | atomically(do |
43 | val <- readTVar counter |
44 | case val of |
45 | 0 -> return val |
46 | otherwise -> retry) |
47 | putStrLn("done") |
$ ghc --make mutex.hs
[1 of 1] Compiling Main ( mutex.hs, mutex.o )
Linking mutex ...
| $ ./mutex
I am thread ThreadId 2
I am thread ThreadId 3
I am thread ThreadId 4
I am thread ThreadId 5
I am thread ThreadId 6
I am thread ThreadId 7
I am thread ThreadId 8
I am thread ThreadId 9
I am thread ThreadId 10
done
|
|