在Haskell中,STM和unsafePerformIO是什么?

7

STM文档中指出:

在atomically中使用unsafePerformIO也是危险的,但原因不同。请参阅unsafeIOToSTM了解更多信息。

当涉及到使用线程和异步异常时,有一些函数用于屏蔽异步异常,以便可以安全地分配和释放资源。

但是有许多函数在幕后使用unsafePerformIO,例如memory包中的allocAndFreeze函数,而且很容易强制执行包含此类表达式的thunk在STM事务内部。这些函数在STM事务内部实际上是安全的吗?是否存在导致内存泄漏或数据损坏的情况?是否有与此情况等价的mask函数?

谢谢

1个回答

4

unsafePerformIO通常使用是安全的,但如果在STM retry时中断IO操作,可能会导致资源泄漏(不一定仅指内存)或数据损坏。这有两个原因:第一,STM重试不运行异常处理程序,因此如果不安全的IO依赖于异常处理程序来释放资源(例如使用bracket),则不会清理资源;第二,IO操作可能在任何时候被中断或执行多次,因此您需要确保即使在中断时也能维护程序不变式。

例如,allocAndFreeze不会泄漏资源,因为它在GHC中使用ForeignPtr内部,这只是托管堆中固定的内存,因此不依赖于异常处理程序或终结器来回收内存。但是,它可能会导致数据损坏,因为如果不安全的IO在数据结构(例如“分配的数组必须始终排序”)中暂时破坏不变式,如果计算在那一点中断,则该破坏可能会变得可见。


谢谢John!你能确认一下STM重试也可以中断FFI调用吗?因此,它可能会在写入某些数据的C例程调用中间中断。 - Scott
1
@Scott: 我对STM的实现不是很熟悉,所以不能确定。我期望如果在不可中断的外部调用期间发生重试,它仍然会等待调用返回,但对于可中断的外部调用,我不知道会发生什么,这可能是未定义的。 - Jon Purdy
1
@Scott:在STM内部执行外部调用的更安全的方法可能是不要这样做,而是在STM之外执行任何外部调用,例如atomically setup; foreignCall; atomically teardown,其中setupteardown获取并释放某种锁(例如TVar Bool);或者您可以在单独的线程中执行它,因此即使事务重试,调用也将正常完成,类似于uninterruptibleUnsafeIOToSTM io = unsafeIOToSTM (do { m <- newEmptyMVar; forkIO ((putMVar m . Right =<< io) `catch` \ (e :: SomeException) -> putMVar m (Left e)); either throw pure =<< takeMVar m }) - Jon Purdy
@john,当然,但我认为这是一个好问题,因为关于这个主题的信息非常稀少。在我看来,Haskell在STM重试的情况下如何中止计算的详细信息将是文档的一个受欢迎的补充,而且我认为作为库的作者,了解这一点是很重要的。如果你使用unsafePerformIO,即使你使用类似于bracket的东西来覆盖异步异常,你的计算仍然可能会在STM中被中断,这一点并不明显。在某些情况下,uninterruptableUnsafeIOToSTM将是一个很好的解决方法。谢谢! - Scott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接