2

考虑以下代码片段

import qualified Foreign.Concurrent
import Foreign.Ptr (nullPtr)

main :: IO ()
main = do
  putStrLn "start"
  a <- Foreign.Concurrent.newForeignPtr nullPtr $
    putStrLn "a was deleted"
  putStrLn "end"

它产生以下输出:

start
end

我本来希望在..a was deleted之后的某个地方看到“”start

我不知道发生了什么事。我有几个猜测:

  • 程序结束时垃圾收集器不会收集剩余的对象
  • putStrLn完成后停止工作main。(顺便说一句,我尝试了与外国进口相同的事情puts并得到了相同的结果)
  • 我的理解ForeignPtr不足
  • GHC 错误?(环境:GHC 6.10.3,英特尔 Mac)

当使用Foreign.ForeignPtr.newForeignPtr而不是Foreign.Concurrent.newForeignPtr它似乎工作时:

{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign.C.String (CString, newCString)
import Foreign.ForeignPtr (newForeignPtr)
import Foreign.Ptr (FunPtr)

foreign import ccall "&puts" puts :: FunPtr (CString -> IO ())

main :: IO ()
main = do
  putStrLn "start"
  message <- newCString "a was \"deleted\""
  a <- newForeignPtr puts message
  putStrLn "end"

输出:

start
end
a was "deleted"
4

1 回答 1

7

Foreign.Foreign.newForeignPtr的文档:

请注意,不能保证在删除最后一个引用后多久执行终结器;这取决于 Haskell 存储管理器的详细信息。实际上,不能保证终结器会被执行。程序可能会在终结器未完成的情况下退出。

因此,您遇到了未定义的行为:即,任何事情都可能发生,并且它可能会因平台而异(正如我们在 Windows 下看到的那样)或发布到发布。

Foreign.Concurrent.newForeignPtr的文档可能暗示了您在两个函数之间看到的行为差异的原因:

这些终结器必须在单独的线程中运行......

如果 Foreign.Foreign 版本的函数的终结器使用主线程,但 Foreign.Concurrent 使用单独的线程,则很可能是主线程关闭而不等待其他线程完成它们的工作,所以其他线程永远不会运行终结。

当然,Foreign.Concurrent 版本的文档确实声称,

唯一的保证是终结器在程序终止之前运行。

我不确定他们是否真的应该声称这一点,因为如果终结器在其他线程中运行,他们可以花费任意时间来完成他们的工作(甚至永远阻塞),因此主线程永远不会能够强制程序退出。这将与Control.Concurrent冲突:

In a standalone GHC program, only the main thread is required to terminate in order for the process to terminate. Thus all other forked threads will simply terminate at the same time as the main thread (the terminology for this kind of behaviour is "daemonic threads").

于 2009-06-21T05:52:21.790 回答