Haskell FFI:ForeignPtr似乎没有被释放(也许是一个GHC错误?)

yai*_*chu 2 garbage-collection haskell ffi

请考虑以下代码段

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"
Run Code Online (Sandbox Code Playgroud)

它产生以下输出:

start
end
Run Code Online (Sandbox Code Playgroud)

我曾希望看到" a was deleted"后地方start..

我不知道发生了什么事.我有一些猜测:

  • 程序完成时,垃圾收集器不会收集剩余的对象
  • putStrLnmain完成后停止工作.(顺便说一句,我和国外进口同样的东西puts也得到了同样的结果)
  • 我的理解ForeignPtr是缺乏的
  • GHC错误?(环境:GHC 6.10.3,Intel 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"
Run Code Online (Sandbox Code Playgroud)

输出:

start
end
a was "deleted"
Run Code Online (Sandbox Code Playgroud)

Cur*_*son 6

来自Foreign.Foreign.newForeignPtr的文档:

请注意,无法保证在最后一次引用被删除后执行终结器的时间.这取决于Haskell存储管理器的细节.实际上,并不能保证终结器完全被执行; 程序可以退出并使用终结器.

所以你遇到了未定义的行为:即,任何事情都可能发生,并且它可能会从平台变为平台(正如我们在Windows下看到的那样)或发布到发布版.

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

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

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

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

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

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

在独立的GHC程序中,只有主线程需要终止才能使进程终止.因此,所有其他分叉线程将简单地与主线程同时终止(这种行为的术语是"守护线程").