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 completesExplanation
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 completeSolution:
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 completeMVars
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: " ++ valueExplanation
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: " ++ valueSolution:
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: " ++ valueSoftware 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 finalCountExplanation
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 finalCountSolution:
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 finalCountSummary
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)
