Haskell中bracket的首个参数为何最多执行一次阻塞操作?
关于Haskell中
bracket实现的疑问与解答 Simon Marlow在《Parallel and Concurrent Programming in Haskell》中给出了bracket的实现代码:
bracket :: IO a -- ^ 先执行的计算("获取资源") -> (a -> IO b) -- ^ 最后执行的计算("释放资源") -> (a -> IO c) -- ^ 中间执行的计算 -> IO c -- 返回中间计算的结果 bracket before after thing = mask $ \restore -> do a <- before r <- restore (thing a) `onException` after a _ <- after a return r
书中后续注释提到:before包含阻塞操作是正常的;若before阻塞时触发异常,不会造成危害。但before应仅执行一次阻塞操作,第二次阻塞操作触发的异常不会执行after。
针对这段注释,疑问及解答如下:
疑问1:为什么before阻塞时触发异常"无危害"?
结合书中takeMVar的示例,若before内部调用takeMVar,当takeMVar返回(MVar已为空)后触发异步异常,是不是因为mask+restore机制,异常会延迟到thing a执行时才处理,此时onException的异常处理器已经就绪,所以不会有问题?
解答
没错,核心逻辑在于mask的作用:bracket的整个逻辑被mask包裹,意味着在mask作用域内,异步异常会被延迟处理,直到进入restore恢复异常响应的代码段。
分两种情况来看:
- 如果
before还在阻塞(比如takeMVar等待取值)时触发异常:此时资源还未被成功获取(takeMVar没拿到值,MVar状态不变),异常直接跳出也不会留下未释放的资源,自然"无危害"。 - 如果
before已经执行完成(比如takeMVar成功拿到值)后触发异常:异常会被mask延迟,直到进入restore (thing a)代码段。此时onException已经绑定了after a作为异常处理器,一旦异常触发,after a会立刻执行,释放已获取的资源,不会出现泄漏。
疑问2:before执行第二次阻塞操作会出现什么问题?
是不是因为异常可能在第二次takeMVar阻塞时触发,此时未屏蔽异常,异常会跳出bracket,导致第一个takeMVar占用的MVar未被after释放,处于非初始状态?
解答
完全正确。假设before是这样的逻辑:
before = do m1 <- takeMVar mv1 -- 第一次阻塞,成功获取资源 m2 <- takeMVar mv2 -- 第二次阻塞,等待时触发异步异常
当执行到第二次takeMVar mv2阻塞时:
- 此时
before还没执行完,a未完成绑定,onException的异常处理器还未就绪。 - 虽然
bracket外层有mask,但Haskell的阻塞操作(如takeMVar)会在等待时临时解除异常屏蔽——这是为了避免线程在阻塞时无法被中断。所以此时异步异常可以直接触发,线程会立刻跳出bracket逻辑。 - 最终结果是第一个
takeMVar mv1已经把mv1取空,但after从未执行,mv1永远处于空状态,造成资源泄漏。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

