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

  1. Threads: Lightweight processes that can run concurrently.
  2. MVars: Mutable variables used for synchronization between threads.
  3. 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.

© Copyright 2024. All rights reserved