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 doneThe order of workers starting up and finishing is likely to be different for each invocation.
In this Haskell version:
We use the
asynclibrary which provides high-level concurrency abstractions.The
workerfunction is similar to the original, but usesthreadDelayinstead oftime.Sleep.In the
mainfunction, we userunConcurrentlyandConcurrentlyto run multiple tasks concurrently. This replaces the explicit use of a WaitGroup.The
forM_function is used to iterate over the range[1..5]and create aConcurrentlytask for each worker.Haskell’s type system ensures that all tasks are completed before the program exits, so there’s no need for an explicit “wait” call.
Error handling in concurrent Haskell code typically involves using
ExceptTor similar monads. For more advanced concurrency patterns, thelifted-asyncpackage 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.