Skip to content

Instantly share code, notes, and snippets.

@monadplus
Created January 4, 2020 22:30
Show Gist options
  • Save monadplus/28dda90f78a26820bbb5a90dfdd8290c to your computer and use it in GitHub Desktop.
Save monadplus/28dda90f78a26820bbb5a90dfdd8290c to your computer and use it in GitHub Desktop.
Shared Concurrent Data Structures

Shared Concurrent Data Structures

Brief summarize of shared state options, with a focus on the performance implications of the different choices.

Typically, the best approach when you want some shared state is to take an existing pure data structure, such as a list or a Map, and store it in a mutable container.

There are a couple of subtle performance issues to be aware of, though.

The first is the effect of lazy evaluation when writing a new value int othe container, which we've already covered.

The second is the choice of mutable contaienr itself, which exposes some subtle performance trade-offs. There are three choices:

  • MVar: we found in Limiting the Number of Threads with a Semaphore that using an MVar to keep a shared counter did not perform well under high contention. This is a consequence of the fairness guarantee that MVar offers.

  • TVar: sometimes performs better than MVar under contention and has the advantage of being composable with other STM operations. However, be aware of the other performance pitfalls with STM described in Performance.

  • IORef: using an IORef together with atomicModifyIORef is often a good choice for performance. The main pitfall here is lazy evaluation; getting enough strictness when using atomicModifyIORef is quite tricky. This is a good pattern to follow:

b <- atomicModifyIORef ref
        (\x -> let (a, b) = f x
               in (a, a `seq` b))
b `seq` return b

The seq call on the last line forces the second component of the pair, which itself is a seq call that forces a, which in turn forces the call to f.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment