haskellconcurrency

TVar that blocks read until change?


I'm trying to wrap my head around how to correctly communicate between threads in Haskell.

I have multiple threads that read some state, and whenever it changes they need to do some update. Then I have multiple threads that can change that state.

I first looked at MVar, but that won't do because it blocks when writing and only one thread can read.

So best thing I found is a TVar, I can write to it and read the current state from it. So I could just have the threads that read poll the state for changes every second or so (that small delay doesn't really matter, nor does it matter that it's possible a thread will miss in-between states). That would work.

However, I was wondering, is there a way that doesn't need to polling? Some other primitive I could use? Or a way I can "listen" to a TVar?


Solution

  • Simply read and retry when the value is the same as before.

    onChanges :: Eq a => (a -> IO ()) -> TVar a -> IO b
    onChanges f tvar = readTVarIO tvar >>= go where
        go a = do
            a' <- atomically do
                a' <- readTVar tvar
                when (a == a') retry
                pure a'
            f a'
            go a'
    

    Don't worry -- this isn't expensive. Each retry will block until the TVar is written to again. If the equality check is expensive compared to running f, or if you want to run f on every write even if the value hasn't changed, you can additionally store an Int or similar in your TVar saying how many times it's been written, and check that to decide whether to retry or not.