当使用带有 Ptr 的括号作为资源时,可以用ForeignPtr 替换它吗?

tar*_*leb 4 haskell ffi

我的代码使用了一个可以描述为指针的资源;为了简单起见,我将在这里使用void指针。资源必须在计算完成后关闭,因此该Control.Exception.bracket函数是确保发生错误时代码不会泄漏的自然选择:

run :: (Ptr () -> IO a) -> IO a
run action = bracket acquireResource closeResource action
-- no eta reduction for clarity
Run Code Online (Sandbox Code Playgroud)

此模式的缺点是资源将始终在action完成后关闭。AFAIU 这意味着不可能做类似的事情

cont <- run $ \ptr -> do
  a <- someAction ptr
  return (\x -> otherActionUsingResource ptr a x)
cont ()
Run Code Online (Sandbox Code Playgroud)

cont执行时资源已关闭。现在我的方法是使用ForeignPtr代替:

run' :: (ForeignPtr () -> IO a) -> IO a
run' action = do
  ptr <- acquireResource
  foreignPtr <- newForeignPtr closeResourceFunPtr ptr
  action foreignPtr
Run Code Online (Sandbox Code Playgroud)

现在看来这大致相当于第一个版本,除了细微的打字差异和资源关闭延迟之外。然而,我确实想知道这是真的,还是我错过了什么。某些错误条件是否会导致这两个版本产生不同的结果?以这种方式使用ForeignPtr安全吗?

dfe*_*uer 5

如果您想这样做,我建议您避免这样做run',这会让您看起来像是要关闭资源。而是做这样的事情。

acquire :: IO (ForeignPtr ())
acquire action = mask $ \unmask -> do
  ptr <- unmask acquireResource
  newForeignPtr closeResourceFunPtr ptr
Run Code Online (Sandbox Code Playgroud)

正如 Carl 在评论中指出的那样,在获取资源和安装终结器以关闭资源之间屏蔽异常非常重要;否则,可能会在两者之间传递异步异常并导致资源泄漏。

此类问题的挑战在于您将其留给用户和/或垃圾收集器来确保资源得到释放。基于原始的Ptr代码使生命周期变得明确。现在不是了。许多人认为,明确的生命周期对于关键资源来说更好。什么ForeignPtr给你,由GC自动完成,这些人认为设计很糟糕。所以要仔细考虑一下!malloc这是您最终只想释放的廉价资源(例如一点ed 内存)吗?或者它是您真正想要确定的昂贵的东西(例如文件描述符)?


旁注:Ptr ()并且ForeignPtr ()不是很惯用。通常类型参数应该是 Haskell 类型,表示所指向的内容。

  • 我想对那里的异步异常小心一点。`newForeignPtr` 进行分配,因此未屏蔽的异步异常可能会导致它在分配之后但在注册终结器之前中止线程。 (3认同)