multithreadingperformancehaskellconcurrencyioref

Atomically modifying multiple IORefs in concurrent Haskell


The documentation for atomicModifyIORef states the following:

This function is useful for using IORef in a safe way in a multithreaded program. If you only have one IORef, then using atomicModifyIORef to access and modify it will prevent race conditions.

Extending the atomicity to multiple IORefs is problematic, so it is recommended that if you need to do anything more complicated then using MVar instead is a good idea.

I'm not sure to understands what this precisely mean. Suppose for instance I have two completely independent IORefs, is atomicity not guaranteed ? If this is the case, what is the point of atomicModifyIORef ? It seems very dangerous to use...

Now I'm aware of STM, but for CAS (compare-and-swap) implementation, which is what I need, it apparently runs slower. Indeed, this article says, p. 5

In terms of performance, the atomicModifyIORef version of CAS is vastly superior to the STM encoding of CAS.

So my questions are the following :

  1. When exactly is the atomicity broken when using atomicModifyIORef multiple IORefs in a multithreaded environment ?
  2. Why is it "problematic" to extend atomicity to multiple IORefs ? Any reference on the subject ?

Solution

  • A modify of an IORef embodies a read followed by a write. So if you write a naïve implementation of modify like this:

    do
      old <- readIORef myVar
      writeIORef myVar $ f old
    

    then you have a potential race condition if two threads execute this code at the same time.

    atomicallyModifyIORef guarantees to do this operation atomically, so the first thread will complete the update before the second one starts.

    However if you have two IORefs then you can't do it this way. Say you want to transfer some value from one to the other, you can decrement one atomically, and then increment the other atomically, but between those two operations there is a period when the sum is in transit, and so the state is inconsistent.

    Extending atomicity to multiple IORefs would require a lot of extra overhead for tracking which IORefs are included in a transaction, storing values in case of a rewind etc. This would be paid for any use of IORefs even if no atomic transactions are done. So its a bad idea. That's what TRefs are for.