atomicModifyIORef的额外结果参数的目的是什么?



modifyIORef的签名足够简单:

modifyIORef :: IORef a -> (a -> a) -> IO ()
不幸的是,这不是线程安全的。有一个替代方法可以解决这个问题:
atomicModifyIORef :: IORef a -> (a -> (a,b)) -> IO b

这两个函数到底有什么不同?当修改可能从另一个线程读取的IORef时,我应该如何使用b参数?

额外的参数用于提供返回值。例如,您可能希望能够自动替换存储在IORef中的值并返回旧值。你可以这样做:

atomicModifyIORef ref (old -> (new, old))

如果没有要返回的值,可以使用以下命令:

atomicModifyIORef_ :: IORef a -> (a -> a) -> IO ()
atomicModifyIORef_ ref f =
    atomicModifyIORef ref (val -> (f val, ()))

modifyIORef具有相同的签名。

正如您在评论中所述,如果没有并发性,您就可以编写像

这样的内容
modifyAndReturn ref f = do
  old <- readIORef ref
  let !(new, r) = f old
  writeIORef r new
  return r

但是在并发上下文中,其他人可以改变读和写之间的引用

我是这样理解的。考虑遵循括号习惯的函数,例如

withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r

这些函数接受一个函数作为实参并返回该函数的返回值。atomicModifyIORef与此相似。它接受一个函数作为参数,目的是返回该函数的返回值。只有一个复杂的地方:参数函数也必须返回一个新值来存储在IORef中。因此,atomicModifyIORef要求该函数返回两个值。当然,这种情况与括号情况并不完全相似(例如,没有涉及IO,我们不处理异常安全等),但是这个类比给了您一个想法。

我喜欢通过State单子来查看这一点。有状态操作修改一些内部状态,并产生一个输出。这里状态在IORef中,结果作为IO操作的一部分返回。因此,我们可以使用State将函数重新表述如下:

import Control.Monad.State
import Data.IORef
import Data.Tuple (swap)
-- | Applies a stateful operation to a reference and returns its result.
atomicModifyIORefState :: IORef s -> State s a -> IO a
atomicModifyIORefState ref state = atomicModifyIORef ref (swap . runState state)

相关内容

  • 没有找到相关文章

最新更新