GHC的thunk是如何原子的?
GHC如何处理多线程访问的thunk(显式线程或评估火花的内部线程)? 多线程能否评估同一个thunk,重复工作? 或者,如果thunk是同步的,怎么样,让表现不受影响?
从链接的博客文章@Lambdageek,GHC评论和GHC用户指南I拼凑在一起:
GHC试图阻止重新评估thunk,但由于线程之间的真正锁定是昂贵的,并且thunk通常是纯粹的并且对重新评估无害,所以它通常是以一种马虎的方式进行的,并且无论如何都有很小的重复工作机会。
它用于避免工作的方法是用黑洞来替换thunk,这是一个告知其他线程(或者有时候,线程本身,这就是<<loop>>
检测发生的情况)的特殊标记,正在评估该thunk。
鉴于此,至少有三种选择:
默认情况下,它使用“lazy blackholing”,这只在线程即将暂停之前完成。 然后它“走”它的堆栈并为新的thunk创建“真正的”blackholes,使用锁定来确保每个thunk只有一个线程拒绝它,并且如果它发现另一个线程已经破坏了thunk,则放弃它自己的评估。 这样比较便宜,因为它不需要考虑评估时间太短以至于完全适合两次暂停之间的thunk。
使用-feager-blackholing-flag
,只要一个thunk开始评估,就会创建blackholes,而如果您正在进行大量的并行处理,则用户指南会建议您这样做。 但是,因为锁定每一个thunk会太昂贵,所以这些blackholes是更便宜的“渴望”的,它不与其他线程同步(尽管其他线程在没有竞争条件时仍然可以看到它们)。 只有当线程暂停时,才会变成“真正的”黑洞。
第三个案例,博客文章特别关注,它被用于像unsafePerformIO
这样的特殊功能,因为它不止一次地评估thunk是有害的。 在这种情况下,线程使用真正的锁定的“真正的”黑洞,但立即创建它,通过在真正的评估之前插入人工线程暂停。
总结在评论中链接到的文章:GHC中的thunk在多个线程之间并不是严格原子的:在多线程竞争条件下评估相同的thunk和复制工作是可能的。 但是,这在实践中并不是什么大问题,因为:
unsafePerformIO
可能是一个问题,但事实证明, unsafePerformIO
会小心避免重新运行相同的IO操作。 因为所有的值都是thunk,所以大部分的thunk是相当小的,所以偶尔重复这个工作来强制一个在编程速度方面并不是什么大不了的。 你可能会想象,复制是很昂贵的,例如, last [1,2..10000000]
因为整个计算很昂贵。 但当然,真正的最外层的thunk只是解决了另一个thunk,如:
case [1,2..10000000] of
[x] -> x
(_:xs) -> last xs
[] -> error "empty list"
如果两个线程重复的工作把调用last
到使用的case
,这是在事物的宏伟计划相当便宜。
是的,有时可以通过多个线程来评估相同的thunk。 GHC运行时会尽量减少重复工作的可能性,所以在实际中很少。 请参阅“共享内存多处理器上的Haskell”以了解低级细节,主要是“无锁Thunk评估”部分。 (我会推荐每个专业的haskell开发人员顺便说一下这篇论文。)
链接地址: http://www.djcxy.com/p/43229.html