10

的签名modifyIORef很简单:

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

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

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

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

4

4 回答 4

12

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

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

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

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

它具有与 相同的签名modifyIORef

于 2016-09-22T13:36:37.770 回答
2

这就是我的理解。想想括号成语后面的函数,例如

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

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

于 2016-09-24T20:54:21.400 回答
2

正如您在评论中所说,如果没有并发,您就可以编写类似的东西

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

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

于 2016-09-25T00:03:13.600 回答
1

我喜欢查看这个的方式是通过Statemonad。有状态的操作会修改一些内部状态并额外产生一个输出。这里状态在一个内部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)
于 2018-01-20T13:11:57.103 回答