请问我实现的Haskell自动并行机制pForkIO是否具备异常安全性?
解答:pForkIO的异常安全性与初始并发能力问题
好的,咱们来逐个解决你的问题:
一、pForkIO的异常安全性:有瑕疵,需要调整
你的思路里用bracket_来保证并行能力的恢复是非常正确的方向——毕竟bracket_的核心就是不管中间动作成功还是抛出异常,清理逻辑都会执行。但你的实现里藏着一个竞态条件,会破坏异常安全性:
看你清理阶段的代码:
do {cap2 <- getNumCapabilities; setNumCapabilities (cap2 + 1)}
这里你重新调用getNumCapabilities来获取当前值,然后加1。但如果有其他线程同时在修改cap值,这个操作就会出错。举个例子:
- 线程A拿到初始cap=8,把它改成7
- 线程B拿到cap=7,改成6
- 线程A的任务抛出异常,进入清理:它拿到当前cap=6,改成7
- 但线程A原本应该把cap从7恢复回8,现在却变成了7——这就导致全局的并行能力被错误地减少了1,后续所有线程都会拿到错误的cap值
正确的做法是保存初始获取的cap值,清理时直接用这个原值恢复,不用重新获取。修改后的代码如下:
import Control.Concurrent import Control.Exception pForkIO :: IO a -> IO a pForkIO action = do cap <- getNumCapabilities if cap == 1 then action else runInBoundThread $ bracket_ (setNumCapabilities (cap - 1)) (setNumCapabilities (cap + 1)) -- 直接用最初的cap值,避免竞态 action
这样修改后,不管其他线程怎么折腾cap值,当前线程都会准确地把cap恢复到它修改前的状态,结合bracket_的异常保证,这才是真正的异常安全。
另外补充一点:setNumCapabilities本身是线程安全的,GHC已经帮我们处理了它的原子性问题,所以不用担心里边的并发冲突。
二、为什么getNumCapabilities初始返回1?
这是GHC runtime的默认行为:默认情况下,GHC只启用1个并发能力(capability),哪怕你有多个CPU核心。要让程序利用多核心,你有两个办法:
运行时加参数:启动程序时加上
+RTS -N参数,比如:./your-program +RTS -N8-N8指定使用8个capabilities,对应你的8个处理器;如果只写-N,GHC会自动检测并使用所有可用核心。程序内手动设置:在
main函数开头主动设置cap数量:main = do setNumCapabilities 8 -- 直接设置为你的处理器数量 -- 后续业务代码这样程序启动后,
getNumCapabilities就会返回你设置的值了。
内容的提问来源于stack exchange,提问作者Dannyu NDos
相关产品推荐
相关产品推荐

