В некоторых ситуациях это имеет смысл, но в целом вы не должны этого делать. Изучите следующее:
doPar =
let a = unsafePerformIO $ someIOCalc 1
b = unsafePerformIO $ someIOCalc 2
in a `par` b `pseq` a+b
в doPar
, вычисляется для a
, затем основной поток оценивает b
. Но возможно, что после того, как основной поток завершит вычисление b
, он также начнет оценивать a
. Теперь у вас есть два потока, оценивающих a
, что означает, что некоторые из операций ввода-вывода будут выполняться дважды (или, возможно, больше). Но если один поток завершит оценку a
, другой просто отбросит то, что он сделал до сих пор. Для того, чтобы это было безопасно, вам нужно несколько вещей, чтобы быть правдой:
- Безопасно, если действия ввода-вывода выполняются несколько раз.
- Безопасно выполнять только некоторые действия ввода-вывода (например, нет очистки)
- Действия IO свободны от любых условий гонки. Если один поток изменяет некоторые данные при оценке
a
, будет ли другой поток, также работающий над a
, вести себя разумно? Наверное, нет.
- Любые иностранные вызовы реентерабельны (это необходимо для общего параллелизма, конечно)
Если ваш someIOCalc
выглядит так
someIOCalc n = do
prelaunchMissiles
threadDelay n
launchMissiles
это абсолютно не безопасно использовать с par
и unsafePerformIO
.
Теперь, стоит ли это когда-нибудь? Может быть. Искры дешевы, даже дешевле, чем потоки, поэтому теоретически это должно быть повышение производительности. На практике, возможно, не так много. У Романа Лещинского есть сообщение в блоге об этом .
Лично мне было гораздо проще рассуждать о forkIO
.