Skip to content

Commit fc45d22

Browse files
committed
Started another MVar tutorial.
1 parent d5e5fde commit fc45d22

File tree

3 files changed

+59
-0
lines changed

3 files changed

+59
-0
lines changed

src/Ex3SharedState.hs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module Ex3SharedState where
2+
3+
import Ex1Threads (sleepMs)
4+
5+
import Control.Concurrent (forkIO)
6+
import Control.Concurrent.MVar (newMVar, takeMVar, modifyMVar_)
7+
import Control.Monad (replicateM)
8+
9+
main = do
10+
counter <- newMVar 0
11+
12+
let increment = modifyMVar_ counter (\c -> return $! c + 1)
13+
let incrementer = do
14+
replicateM 1000 increment
15+
return ()
16+
17+
threads <- replicateM 5 (forkIO incrementer)
18+
19+
sleepMs 10
20+
count <- takeMVar counter
21+
print count

src/Main.hs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module Main where
33

44
import qualified Ex1Threads (main)
55
import qualified Ex2MVars (main)
6+
import qualified Ex3SharedState (main)
67
import qualified Ex3Channels (main)
78
import qualified Ex4DuplicatingChannels (main)
89

@@ -11,6 +12,7 @@ main :: IO ()
1112
main = do
1213
example "Threads" Ex1Threads.main
1314
example "MVar" Ex2MVars.main
15+
example "Shared state" Ex3SharedState.main
1416
example "Channels" Ex3Channels.main
1517
example "Duplicating channels" Ex4DuplicatingChannels.main
1618

src/tutorial.md

+36
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ The availability of these features is comparable to built-in language features l
2323

2424
* [Basic threading](#basic-threading)
2525
* [Thread synchronisation with MVars](#thread-synchronisation-with-mvars)
26+
* [Sharing state with MVars](#sharing-state-with-mvars)
2627
* [Channels](#channels)
2728
* [Duplicating channels](#duplicating-channels)
2829

@@ -139,6 +140,41 @@ Running this, you should see the following output:
139140

140141
[See the whole program.](./Ex2MVars.hs)
141142

143+
## Sharing state with MVars
144+
145+
In the [previous example](#thread-synchronisation-with-mvars), we used an `MVar` as a way to wait for a concurrent action's result.
146+
Sometimes we want long-running actions to share state, and we want to make sure they don't run into race conditions when modifying that state.
147+
`MVars` can also fill this role, though we'll see a much better way to do it when we look at STM later.
148+
149+
For now, we'll reproduce a classic concurrency example: we have a single counter, which multiple threads try to increment several times.
150+
In the presence of race conditions, errors will mean often the final result isn't what you would expect, if you were to run each thread synchronously.
151+
152+
```haskell
153+
main = do
154+
counter <- newMVar 0
155+
156+
let increment = modifyMVar_ counter (\c -> return $! c + 1)
157+
let incrementer = do
158+
replicateM 1000 increment
159+
return ()
160+
161+
threads <- replicateM 5 (forkIO incrementer)
162+
163+
sleepMs 10
164+
count <- takeMVar counter
165+
print count
166+
```
167+
168+
Here, `increment` uses `modifyMVar_` to apply a function to the contents of an `MVar`, when it exists.
169+
This operation blocks, naturally, and also blocks other threads from operating on the `MVar`'s contents at the same time.
170+
171+
`increment` uses `$!`, which is a _strict_ form of function application, to avoid simply storing a huge `(0 + (1 + (1 + ...` think inside `counter`.
172+
This strict application ensures that `c + 1` is evaluated before it is stored in the `MVar`.
173+
174+
Note that in this case, our application is essentially single-threaded, because all threads block on `counter`.
175+
So we gain no _time_ efficiency from using strict application (`$!`) - we could just as easily build up a huge thunk, then have the main thread evaluate it when we `print count`.
176+
However, this costs memory, so evaluating the counter strictly at every step makes the program more _space_ efficient.
177+
142178
## Channels
143179

144180
Now that we've talked about `MVar`s, let's get up to speed with channels in Haskell.

0 commit comments

Comments
 (0)