If I search for IORef a -> (a -> (a, b)) -> IO b on Hoogle, the first result is
atomicModifyIORef :: IORef a -> (a -> (a, b)) -> IO b
base Data.IORef
Atomically modifies the contents of an
IORef.This function is useful for using
IORefin a safe way in a multithreaded program. If you only have oneIORef, then usingatomicModifyIORefto access and modify it will prevent race conditions.Extending the atomicity to multiple
IORefsis problematic, so it is recommended that if you need to do anything more complicated then usingMVarinstead is a good idea.
atomicModifyIORefdoes not apply the function strictly. This is important to know even if all you are doing is replacing the value. For example, this will leak memory:ref <- newIORef '1' forever $ atomicModifyIORef ref (\_ -> ('2', ()))Use
atomicModifyIORef'oratomicWriteIORefto avoid this problem.This function imposes a memory barrier, preventing reordering; see Data.IORef#memmodel for details.
(I'm not sure why, if I click on any of the three links, the resulting doc page doesn't seem to contain the text will leak memory, which is contained in the excerpt above.)
The question is two folds:
- Why is the above example leaking memory?
- Why the leak doesn't happen if
atomicModifyIORef'is used instead ofatomicModifyIORef?
Let's run the code.
After this line, the contents of the IORef is just
'1'.Let's apply once this action:
After this line, the contents of the IORef is
(\_ -> '2') '1'. Note that, because of laziness, this is not simplified to'2', but is kept as an unevaluated thunk. (atomicModifyIORef'would instead simplify that.)Once more, let's apply this action:
Now the contents of the IORef is
(\_ -> '2') ((\_ -> '2') '1').And so on, see the pattern? We build larger and larger unevaluated thunks, wasting memory, when we could (and should) simplify its contents.