I am trying to work out the async model of Haskell and have troubles matching the known concepts against what Haskell does. I have the following code:
module Main where
import Control.Concurrent.Async (concurrently)
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
(a, b) <- concurrently (pure $ myFibonaci 45) (pure $ myFibonaci 42)
print a
putStrLn "----"
print b
myFibonaci :: Integer -> Integer
myFibonaci 0 = 0
myFibonaci 1 = 1
myFibonaci n = myFibonaci (n - 1) + myFibonaci (n - 2)
Not sure why but logs are printed in the following order:
267914296 -- value of a
----
And only then it starts computing the value of b
267914296
----
102334155 -- value of b
I am a little puzzled since concurrently
should in my understanding spawn 2 green threads which could in turn be mapped to different system threads and end up executing on different cores. Meaning that by the time I get the value of a
the value of b
should have theoretically (if scheduling delays are not due) be computed. I am running code with -threaded
GHC options so this shouldn't be the problem. Is this a case of Haskell lazyness? I suspect the better solution would be to use Control.Parallel
but what is the difference between async
and Control.Parallel
then? I am pretty sure Go which uses a similar model doesn't make such distinctions
This is due to lazyness. The IO
action you are executing is pure
, not myFibonaci xy
, therefore at the line (a, b) <- ...
both elements of the tuple are thunks which are evaluated when printing. Use @jonpurdy's solution: $!
. Two things to consider:
Control.Parallel
API, will force you to choose which "level of lazyness" you want for your parallel execution, via rseq
, rdeepseq
, etc..below you have the fix to your code and the strategies
approach
import Control.Concurrent.Async (concurrently)
import Data.Time.Clock (getCurrentTime)
import Control.Exception
import Control.Parallel.Strategies
-- just an utility
measure :: Show a => a -> IO ()
measure a = do
getCurrentTime >>= print
print a
getCurrentTime >>= print
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
-- if you change $! by $ you'll notice measure b is much longer because
-- computation is done on measure b but with $!, both measure are the same
-- since the calculation is done here
(a, b) <- concurrently (pure $! myFibonaci 15) (pure $! myFibonaci 35)
measure a
putStrLn "----"
measure b
putStrLn "********"
getCurrentTime >>= print
-- strategies interface force you to choose the level of lazyness
-- |- eval in IO in parallel
-- | |- this pure computation
-- | | |- with strategy "parallel tuples with full evaluation of each side of the tuple"
result <- usingIO (myFibonaci 14, myFibonaci 34) (evalTuple2 rdeepseq rdeepseq)
print result
getCurrentTime >>= print
myFibonaci :: Integer -> Integer
myFibonaci 0 = 0
myFibonaci 1 = 1
myFibonaci n = myFibonaci (n - 1) + myFibonaci (n - 2)