Waitgroups in Haskell

In Haskell, we can use the async library to achieve similar functionality to WaitGroups. We’ll use Async and Concurrently to run multiple tasks concurrently and wait for them to finish.

import Control.Concurrent (threadDelay)
import Control.Concurrent.Async
import Control.Monad (forM_)
import Text.Printf (printf)

-- This is the function we'll run in every async task.
worker :: Int -> IO ()
worker id = do
    printf "Worker %d starting\n" id
    -- Sleep to simulate an expensive task.
    threadDelay 1000000  -- 1 second
    printf "Worker %d done\n" id

main :: IO ()
main = do
    -- Run several tasks concurrently and wait for all of them to finish.
    runConcurrently $ forM_ [1..5] $ \i ->
        Concurrently $ worker i

    -- Note that this approach automatically waits for all tasks to finish.
    -- There's no need for an explicit "wait" call.

    -- For more advanced use cases, consider using the 
    -- lifted-async package which provides more powerful 
    -- concurrency primitives.

To run the program:

$ runhaskell waitgroups.hs
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 1 done
Worker 2 done
Worker 3 done
Worker 4 done
Worker 5 done

The order of workers starting up and finishing is likely to be different for each invocation.

In this Haskell version:

  1. We use the async library which provides high-level concurrency abstractions.

  2. The worker function is similar to the original, but uses threadDelay instead of time.Sleep.

  3. In the main function, we use runConcurrently and Concurrently to run multiple tasks concurrently. This replaces the explicit use of a WaitGroup.

  4. The forM_ function is used to iterate over the range [1..5] and create a Concurrently task for each worker.

  5. Haskell’s type system ensures that all tasks are completed before the program exits, so there’s no need for an explicit “wait” call.

  6. Error handling in concurrent Haskell code typically involves using ExceptT or similar monads. For more advanced concurrency patterns, the lifted-async package provides additional tools.

This Haskell implementation showcases how functional languages can handle concurrency with high-level abstractions, often requiring less explicit synchronization code than imperative languages.