modifyIORef
的签名非常简单:
modifyIORef :: IORef a -> (a -> a) -> IO ()
不幸的是,这不是线程安全的。有一个替代方案可以解决这个问题:
atomicModifyIORef :: IORef a -> (a -> (a,b)) -> IO b
这两个功能之间究竟有什么区别?在修改可能从另一个线程读取的b
时,我应该如何使用IORef
参数?
答案 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)