Concurrency in Haskell allows you to perform multiple tasks simultaneously, making your programs more efficient and responsive. This section will cover the basics of concurrency, including threads, synchronization, and common concurrency patterns in Haskell.
Key Concepts
- Threads: Lightweight processes that can run concurrently.
- MVars: Mutable variables used for synchronization between threads.
- STM (Software Transactional Memory): A composable memory transaction system for managing shared state.
Threads
Creating Threads
In Haskell, you can create threads using the forkIO
function from the Control.Concurrent
module.
import Control.Concurrent (forkIO, threadDelay) main :: IO () main = do forkIO $ putStrLn "Hello from the new thread!" putStrLn "Hello from the main thread!" threadDelay 1000000 -- Wait for 1 second to ensure the new thread completes
Explanation
forkIO
: Creates a new thread to run the given IO action.threadDelay
: Pauses the current thread for a specified number of microseconds.
Practical Exercise
Exercise: Create a program that spawns two threads, each printing a different message.
import Control.Concurrent (forkIO, threadDelay) main :: IO () main = do forkIO $ putStrLn "Thread 1: Hello!" forkIO $ putStrLn "Thread 2: Hi there!" threadDelay 1000000 -- Wait for 1 second to ensure both threads complete
Solution:
import Control.Concurrent (forkIO, threadDelay) main :: IO () main = do forkIO $ putStrLn "Thread 1: Hello!" forkIO $ putStrLn "Thread 2: Hi there!" threadDelay 1000000 -- Wait for 1 second to ensure both threads complete
MVars
Using MVars
MVars are mutable variables that can be used for communication between threads.
import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar) main :: IO () main = do mvar <- newEmptyMVar forkIO $ do putStrLn "Child thread: Putting value into MVar" putMVar mvar "Hello from MVar" value <- takeMVar mvar putStrLn $ "Main thread: Got value from MVar: " ++ value
Explanation
newEmptyMVar
: Creates an empty MVar.putMVar
: Puts a value into the MVar.takeMVar
: Takes a value from the MVar, blocking if the MVar is empty.
Practical Exercise
Exercise: Create a program where the main thread waits for a value from a child thread using an MVar.
import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar) main :: IO () main = do mvar <- newEmptyMVar forkIO $ do putStrLn "Child thread: Putting value into MVar" putMVar mvar "Hello from MVar" value <- takeMVar mvar putStrLn $ "Main thread: Got value from MVar: " ++ value
Solution:
import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar) main :: IO () main = do mvar <- newEmptyMVar forkIO $ do putStrLn "Child thread: Putting value into MVar" putMVar mvar "Hello from MVar" value <- takeMVar mvar putStrLn $ "Main thread: Got value from MVar: " ++ value
Software Transactional Memory (STM)
Using STM
STM allows you to perform atomic transactions on shared memory.
import Control.Concurrent.STM import Control.Concurrent (forkIO, threadDelay) main :: IO () main = do counter <- atomically $ newTVar 0 forkIO $ atomically $ modifyTVar' counter (+1) forkIO $ atomically $ modifyTVar' counter (+1) threadDelay 1000000 finalCount <- atomically $ readTVar counter putStrLn $ "Final counter value: " ++ show finalCount
Explanation
atomically
: Runs a transaction atomically.newTVar
: Creates a new transactional variable.modifyTVar'
: Modifies the value of a transactional variable.readTVar
: Reads the value of a transactional variable.
Practical Exercise
Exercise: Create a program that uses STM to safely increment a counter from multiple threads.
import Control.Concurrent.STM import Control.Concurrent (forkIO, threadDelay) main :: IO () main = do counter <- atomically $ newTVar 0 forkIO $ atomically $ modifyTVar' counter (+1) forkIO $ atomically $ modifyTVar' counter (+1) threadDelay 1000000 finalCount <- atomically $ readTVar counter putStrLn $ "Final counter value: " ++ show finalCount
Solution:
import Control.Concurrent.STM import Control.Concurrent (forkIO, threadDelay) main :: IO () main = do counter <- atomically $ newTVar 0 forkIO $ atomically $ modifyTVar' counter (+1) forkIO $ atomically $ modifyTVar' counter (+1) threadDelay 1000000 finalCount <- atomically $ readTVar counter putStrLn $ "Final counter value: " ++ show finalCount
Summary
In this section, you learned about concurrency in Haskell, including how to create and manage threads, use MVars for synchronization, and leverage STM for atomic transactions. These tools and techniques will help you write efficient and responsive Haskell programs.
Next, we will explore parallelism in Haskell, which allows you to perform computations in parallel to further improve performance.
Haskell Programming Course
Module 1: Introduction to Haskell
- What is Haskell?
- Setting Up the Haskell Environment
- Basic Syntax and Hello World
- Haskell REPL (GHCi)