There are situations where it makes sense, but overall you should not do this. Learn the following:
doPar = let a = unsafePerformIO $ someIOCalc 1 b = unsafePerformIO $ someIOCalc 2 in a `par` b `pseq` a+b
in doPar , the calculation for a calculated, then the main thread evaluates b . But it is possible that after the main thread completes the calculation of b , it will also begin to evaluate a . You now have two threads evaluating a , which means that some of the I / O will be performed twice (or possibly more). But if one thread completes the evaluation of a , the other will simply abandon what it has done so far. To be safe, you need a few things to be true:
- It is safe that I / O actions are performed multiple times.
- Safe only for some of the I / O operations (e.g. no cleanup)
- IO actions are free from any race conditions. If one thread mutates some data when evaluating
a , will the other thread working on a behave intelligently? Probably not. - Any external calls are repetitive (you need it for concurrency in general, of course)
If your someIOCalc looks like this
someIOCalc n = do prelaunchMissiles threadDelay n launchMissiles
it is absolutely safe to use this with par and unsafePerformIO .
Now, is it worth it? May be. Sparks are cheap, even cheaper than streams, so theoretically this should be a performance boost. In practice, perhaps not so much. Roman Leshchinsky has a nice blog post about this .
Personally, I found it much easier to talk about forkIO .
John l
source share