42

I find TVar's quite easy to work with even though MVar's appear a little simpler, while TVar's a little more featureful.

So my question is pretty simple, what condition do I want to go to MVar rather than TVar? I suppose anytime I don't need transactional update I can use an MVar, but in what way does that benefit me?

Jimmy Hoffa
  • 5,909
  • 30
  • 53

2 Answers2

47

MVar

  • can be empty
  • used to implement synchronization patterns between threads
  • allows one-way communication between threads
  • can be faster than TVar in some cases

TVar

  • can not be empty
  • atomic transactions
  • "shared memory" between threads; can be used to implement, for example, a lookup cache from which multiple threads can read/write
  • access is linear time in the number of operations in the transaction log
  • long running transactions are vulnerable to starvation if there are many shorter transactions, preventing them from commiting successfully

IORef

  • mutable pointer-like reference
  • often used for destructive IO writes/updates
  • has atomic CAS operations, but complex transactional logic is better suited to a TVar

There is not really a hard and fast rule for when to use MVar or TVar. If the resource I'm guarding will ever be "missing" (as opposed to empty, consider Nothing vs Just mempty), then MVar often makes the most sense. If I will need to perform atomic blocks of modifications to the resource, then TVar is most suitable.

cdk
  • 6,698
  • 24
  • 51
  • 2
    You refer to shared MVar's for thread communication, shouldn't they only be used for one-way communication each though because if both threads are updating the same MVar to talk back and forth they're going to have race conditions? – Jimmy Hoffa Mar 15 '13 at 19:12
  • you're correct. I didn't mean both threads would update the same `MVar`, I'll make that more clear – cdk Mar 15 '13 at 19:14
  • 4
    Might want to add a note about starvation too. MVars are guaranteed to be fair, TVars aren't. – hammar Mar 16 '13 at 10:27
  • 2
    @JimmyHoffa you can have several patterns with `MVar`, e.g: some threads putting others taking (essentially a 1-ary FIFO queue); many threads doing `modifyMVar` to modify some shared state; threads take a `()` before entering a critical section and replace when exiting (using it as a lock), etc. – jberryman May 27 '14 at 22:41
41

TVars are safer but slower.

MVars can deadlock, but are much, much more efficient.

More efficient still is IORef and atomicModifyIORef (CAS), but that's highly restricted in what you can do with it.

It's really a safety over performance trade off. TVars are fully general, very safe. Everything else is less so, on a decreasing scale.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • 1
    Efficiency, good 'nuff. I figured they were similarly performant, so moral of the story: Use a TVar if you need transactional update, otherwise use an MVar **because it will slow down your multi-threaded app considerably less** – Jimmy Hoffa Mar 15 '13 at 19:03
  • 4
    Can anybody quantify the actual speed difference? (Presumably this is tricky to measure since it depends on actual thread interactions...) – MathematicalOrchid Mar 16 '13 at 11:24
  • 2
    @MathematicalOrchid ignoring concurrency issues: reading and writing an `IORef` will be your fastest operations, but GHC 7.8 shipped with some improvements to `MVar` that bring a `takeMVar/putMVar` within a few ns of a `read/writeIORef` (say 9.7/4.6 vs 3.7/7/3 on my machine ()) and we also have a new very fast `readMVar` (when the MVar is full of course). – jberryman May 27 '14 at 22:36
  • 1
    Also `atomicModifyIORef` performs very poorly under contention, for some reason. – jberryman May 27 '14 at 22:37
  • 3
    @MathematicalOrchid I tried to benchmark `TVars` vs `modifyIORef` once. I made some bank account thing where threads would each do transactions between two bank accounts. The `IORef` implementation would end up with inconsistencies obviously. My first attempt managed to bench a random number generator, and my second attempt benched a faster random generator. I got a speed difference (~ twice as fast) on my third attempt where my random generator really wasn't random at all but just taking pregenerated numbers from an array. At that point I realised I didn't know what I was doing so I gave up. – monocell Nov 15 '14 at 16:57