Haskell:异步中纯代码的并行执行

Haskell Parallel execution of pure code in async

提问人:Emulebest 提问时间:11/7/2023 更新时间:11/7/2023 访问量:55

问:

我正在尝试制定 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

我有点困惑,因为在我的理解中应该生成 2 个绿色线程,这些线程反过来可以映射到不同的系统线程并最终在不同的内核上执行。这意味着当我得到 的值时,理论上应该计算出的值(如果调度延迟没有到期)。我正在使用 GHC 选项运行代码,所以这应该不是问题。这是Haskell懒惰的案例吗?我怀疑更好的解决方案是使用,但 和 然后有什么区别?我很确定使用类似模型的 Go 不会做出这样的区分concurrentlyab-threadedControl.ParallelasyncControl.Parallel

多线程异 Haskell 并行处理

评论

2赞 Jon Purdy 11/7/2023
我想这些操作会立即返回,因此它们的结果不会同时计算。您可以使用 或 强制它们。pure $! …evaluate

答:

4赞 lsmor 11/7/2023 #1

这是由于懒惰。您正在执行的操作是 ,而不是 ,因此在行中,元组的两个元素都是在打印时计算的 thunk。使用 @jonpurdy 的解决方案:.需要考虑的两件事:IOpuremyFibonaci xy(a, b) <- ... $!

  • Control.ParallelAPI,将迫使你选择你想要的并行执行的“懒惰程度”,通过、等。rseqrdeepseq
  • 异步代码与并行代码不同。前者可以在一个内核中运行(并且很可能应该运行)进行某种上下文切换,后者必须在多个内核中运行。我推荐 S.Marlow 在 haskell 中进行并行和并发编程,他是为 haskell 编写 par/conc 运行时的人。

下面你对你的代码和方法进行了修复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)