Pet*_*lák 16 haskell exception lazy-evaluation thunk
我创建了这个小程序,它创建了一个长时间运行的thunk,最终因异常而失败.然后,多个线程尝试评估它.
import Control.Monad
import Control.Concurrent
import Control.Concurrent.MVar
main = do
let thunk = let p = product [1..10^4]
in if p `mod` 2 == 0 then error "exception"
else ()
children <- replicateM 2000 (myForkIO (print thunk))
mapM_ takeMVar children
-- | Spawn a thread and return a MVar which can be used to wait for it.
myForkIO :: IO () -> IO (MVar ())
myForkIO io = do
mvar <- newEmptyMVar
forkFinally io (\_ -> putMVar mvar ())
return mvar
Run Code Online (Sandbox Code Playgroud)
增加线程数显然对计算没有影响,这表明失败的thunk会将异常保留为结果.这是真的吗?这种行为是在某处记录/指定的吗?
更新:将forkFinally行更改为
forkFinally io (\e -> print e >> putMVar mvar ())
Run Code Online (Sandbox Code Playgroud)
确认每个线程都出现异常.
Joa*_*ner 12
让我通过使用ghc-heap-view库来展示GHC实际上是如何做到这一点来回答这个问题.你可以重现这个ghc-vis并得到漂亮的图片.
我首先创建一个具有异常值的数据结构:
Prelude> :script /home/jojo/.cabal/share/ghc-heap-view-0.5.1/ghci
Prelude> let x = map ((1::Int) `div`) [1,0]
Run Code Online (Sandbox Code Playgroud)
起初它纯粹是一个thunk(似乎涉及各种类型):
Prelude> :printHeap x
let f1 = _fun
in (_bco [] (_bco (D:Integral (D:Real (D:Num _fun _fun _fun _fun _fun _fun _fun) (D:Ord (D:Eq _fun _fun) _fun _fun _fun _fun _fun _fun _fun) _fun) (D:Enum _fun _fun f1 f1 _fun _fun _fun _fun) _fun _fun _fun _fun _fun _fun _fun) _fun) _fun)()
Run Code Online (Sandbox Code Playgroud)
现在我评估非异常抛出部分:
Prelude> (head x, length x)
(1,2)
Prelude> System.Mem.performGC
Prelude> :printHeap x
[I# 1,_thunk (_fun (I# 1)) (I# 0)]
Run Code Online (Sandbox Code Playgroud)
列表的第二个元素仍然只是一个"正常"的thunk.现在我评估一下,得到一个例外,然后再看一遍:
Prelude> last x
*** Exception: divide by zero
Prelude> System.Mem.performGC
Prelude> :printHeap x
[I# 1,_thunk (SomeException (D:Exception _fun (D:Show _fun _fun _fun) _fun _fun) DivideByZero())]
Run Code Online (Sandbox Code Playgroud)
你可以看到它现在是一个引用一个SomeException对象的thunk .该SomeException数据构造具有类型forall e . Exception e => e -> SomeException,所以在构造的所述第二参数是DivideByZero所述的构造ArithException之外,与第一参数相应的Exception类型的类的实例.
这个thunk可以像任何其他Haskell值一样传递,并且如果被评估,将再次引发异常.并且,就像任何其他值一样,可以共享异常:
Prelude> let y = (last x, last x)
Prelude> y
(*** Exception: divide by zero
Prelude> snd y
*** Exception: divide by zero
Prelude> System.Mem.performGC
Prelude> :printHeap y
let x1 = SomeException (D:Exception _fun (D:Show _fun _fun _fun) _fun _fun) DivideByZero()
in (_thunk x1,_thunk x1)
Run Code Online (Sandbox Code Playgroud)
线程和MVars也会发生同样的事情,没有什么特别之处.