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

时间:2016-09-22 13:25:53

标签: haskell concurrency ioref global-state

modifyIORef的签名非常简单:

modifyIORef :: IORef a -> (a -> a) -> IO ()

不幸的是,这不是线程安全的。有一个替代方案可以解决这个问题:

atomicModifyIORef :: IORef a -> (a -> (a,b)) -> IO b

这两个功能之间究竟有什么区别?在修改可能从另一个线程读取的b时,我应该如何使用IORef参数?

4 个答案:

答案 0 :(得分:12)

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

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

如果您没有返回值,可以使用以下内容:

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

modifyIORef具有相同的签名。

答案 1 :(得分:2)

以下是我对此的理解。考虑括号括号之后的函数,例如

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

这些函数将函数作为参数并返回该函数的返回值。 atomicModifyIORef与此类似。它将函数作为参数,目的是返回该函数的返回值。只有一个复杂因素:参数函数,还要返回一个新值存储在IORef中。因此,atomicModifyIORef要求该函数返回两个值。当然,这个案例与括号案例并不完全相似(例如,没有涉及IO,我们没有处理异常安全等),但这个类比给了你一个想法。

答案 2 :(得分:1)

正如你在评论中所说,没有并发性,你就可以写出类似

的东西
modifyAndReturn ref f = do
  old <- readIORef ref
  let !(new, r) = f old
  writeIORef r new
  return r

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

答案 3 :(得分:1)

我喜欢看的方式是通过State monad。有状态操作修改一些内部状态并另外产生输出。这里的状态在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)