Does it? I have always thought the use case for the pooled*Concurrently
family of functions was this exact scenario. In fact, I have modified the code @MangoIV provided to include the pooledMapConcurrently
variant, and they seem to behave identical (wrt to the scheduling behavior). The output order seems to differ, but you mentioned that it is not important here:
{-# LANGUAGE BlockArguments #-}
module Main where
import UnliftIO
import UnliftIO.Concurrent
import System.Environment (getArgs)
import Data.Functor (($>))
work :: [IO a] -> IO [a]
work jobs = do
sem <- newQSem =<< getNumCapabilities
go sem [] jobs
where
go _ acc [] = traverse wait acc
go sem acc (job : jobs) = do
as <- do
waitQSem sem
async do
job `finally` signalQSem sem
go sem (as : acc) jobs
work2 :: [IO a] -> IO [a]
work2 = pooledMapConcurrently id
main :: IO ()
main = do
[variant] <- getArgs
setNumCapabilities 2
print =<< getNumCapabilities
worker <-
if variant == "1"
then putStrLn "handrolled variant" $> work
else putStrLn "pooledMapConcurrently variant" $> work2
print =<< worker (threads [n ^ 2 | n <- [10, 5, 1]])
where
threads = map \n -> do
tid <- myThreadId
let m = n * 100_000
putStrLn (show tid <> ": sleeping " <> show m)
threadDelay m
putStrLn (show tid <> ": waking up")
pure n
and the outputs:
$ cabal exec tmp-map-concurrently 1
2
handrolled variant
ThreadId 8: sleeping 2500000
ThreadId 7: sleeping 10000000
ThreadId 8: waking up
ThreadId 9: sleeping 100000
ThreadId 9: waking up
ThreadId 7: waking up
[1,25,100]
$ cabal exec tmp-map-concurrently 2
2
pooledMapConcurrently variant
ThreadId 8: sleeping 2500000
ThreadId 7: sleeping 10000000
ThreadId 8: waking up
ThreadId 8: sleeping 100000
ThreadId 8: waking up
ThreadId 7: waking up
[100,25,1]