Haskell 并行执行纯代码的异步操作

4
我正在努力理解Haskell的异步模型,并且在将已知的概念与Haskell的实现进行匹配时遇到了困难。 我有以下代码:
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)

不确定为什么,但日志以以下顺序打印:
267914296 -- value of a
----

然后它才开始计算b的值。
267914296
----
102334155 -- value of b

我有点困惑,因为根据我的理解,concurrently应该会生成两个绿色线程,然后可以映射到不同的系统线程并在不同的核心上执行。这意味着当我获取a的值时,理论上b的值应该已经计算出来了(如果没有调度延迟的话)。我正在使用-threaded GHC选项运行代码,所以这不应该是问题。这是Haskell的惰性求值的问题吗?我怀疑更好的解决方案是使用Control.Parallel,但是asyncControl.Parallel之间有什么区别呢?我相当确定使用类似模型的Go语言没有这样的区别。

2
我猜这些操作会立即返回,所以它们的结果不会同时计算。你可以使用pure $! …evaluate来强制执行它们。 - undefined
1个回答

4
这是由于懒惰导致的。你正在执行的IO操作是pure,而不是myFibonaci xy,因此在(a, b) <- ... 这一行,元组的两个元素都是需要在打印时进行求值的thunk。使用@jonpurdy的解决方案:$!。有两件事需要考虑:
  • Control.Parallel API会强制你选择并行执行时的“懒惰程度”,通过rseqrdeepseq等。
  • 异步代码和并行代码不同。前者可以在一个核心上运行(并且很可能应该运行),进行某种形式的上下文切换,而后者必须在多个核心上运行。我推荐阅读S.Marlow的《Haskell并行和并发编程》这本书,他是编写Haskell的并行/并发运行时的人。
下面是修复你的代码和使用strategies方法的解决方案。
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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接