关于《Parallel and Concurrent Programming in Haskell》严格求值示例的疑问
对该MVar优化实现的原理说明
原实现的核心问题
Simon Marlow提到的原始写法:
putMVar m (Map.insert name number book)
最大的问题不是存了未求值表达式,而是会累积跨多次插入的线性长度操作链,带来两个严重问题:
- 锁持有时间过长:下次有线程调用
takeMVar读取Map时,需要持有MVar锁完成整条长链的求值,锁持有时间随插入次数线性增长,并发性能急剧下降 - 空间泄漏:整条thunk链会持有所有历史版本的Map引用,导致无用的旧数据无法被GC回收
优化写法的作用原理
推荐的优化写法:
let book' = Map.insert name number book putMVar m book' seq book' (return ())
你提到的seq仅求值到弱首范式(WHNF)是完全正确的:对于Data.Map的实现来说,求值到WHNF只会得到最外层的树节点构造器(Bin 或 Tip),内部子节点、节点存储的键值确实仍然是未求值状态,但这个操作刚好解决了上面的核心问题:
seq book' (return ())会在当前线程、putMVar释放锁之后,立刻完成本次Map.insert的所有树调整、重平衡逻辑,把原来的Map.insert name number bookthunk直接转成一个Map结构的根节点- 多次插入之后不会形成
insert kN (insert kN-1 (...))的线性长链,每个插入操作的计算都在写入线程单独完成,不会把计算成本转嫁到后续持有MVar锁的线程上 - 旧版本的Map不会被长thunk链持有,没有被其他地方引用的旧数据可以被正常GC,避免了空间泄漏
补充说明
内部子节点的未求值状态是Data.Map惰性设计的正常特性,这些子节点的thunk仅会在访问对应键的时候才会求值,本身不会形成线性长度的累积链,也不会带来额外的空间和性能问题。如果业务场景需要完全求值所有键值,可以替换seq为deepseq,但绝大多数场景下仅求值到WHNF的性价比最高。
内容的提问来源于stack exchange,提问作者Николай Николай
相关产品推荐
相关产品推荐

