par
功能如何运作?它的签名是:
par :: a -> b -> b.
但这很奇怪。为什么不呢:
par :: (a -> b) -> a -> b
(获取函数,在新线程中执行并返回结果)?
另一个问题,这是正常的haskell多线程吗?
答案 0 :(得分:10)
par
用于推测并行,并依赖于懒惰。
您推测,在您忙于处理a
时,应计算未评估的b
。
稍后在您的计划中,您可能会再次引用a
,它就会准备就绪。
这是一个例子。我们希望将3个数字加在一起。每个数字的计算成本都很高。我们可以并行计算它们,然后将它们加在一起:
main = a `par` b `par` c `pseq` print (a + b + c)
where
a = ack 3 10
b = fac 42
c = fib 34
fac 0 = 1
fac n = n * fac (n-1)
ack 0 n = n+1
ack m 0 = ack (m-1) 1
ack m n = ack (m-1) (ack m (n-1))
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
答案 1 :(得分:4)
因为我们不执行功能"在哈斯克尔。我们评估价值观,这是我们控制处理器活动的方式。 par x y
基本上做了什么:在评估结果y
时,运行时也已经预先评估x
,尽管还没有要求它。
请注意,这不一定是现在编写并行代码的最佳方式。查看更新的替代方案,例如Eval
monad。您可能需要阅读Simon Marlow's book。
答案 2 :(得分:1)
除了之前的答案之外,值得指出的是a
和b
将仅被评估为弱头正常形式(WHNF)(即应用最外层的缩减或构造函数),因此使用deepseq
强制进行评估可能很有用。
在操作语义方面par
创建一个 spark ,它是一个指向thunk(未评估计算)的指针并添加到spark池中。这非常便宜,可能有数百万的火花。线程创建是建议性的,运行时系统可以决定不将a
转换为线程并通过忽略spark或通过在父级中包含子spark来修剪superfluos parallelism。
您显示的图片可能表示您的代码存在问题,其中在CPU2上执行的线程执行的工作量明显减少(负载不平衡)。