How do I wait for a few MVars?
I need a function
takeAnyMVar :: NonEmpty (MVar a) -> IO (MVar a, a)
which waits for multiple MVar
and returns the first MVar
(and its value) that becomes available.
In particular, it should only cause one of MVar
the input list to be in an empty state that was not empty before.
I have an implementation, but it is inefficient and incorrect:
import Data.List.NonEmpty -- from semigroups
import Control.Concurrent.Async -- from async
import Control.Concurrent.MVar
import Control.Applicative
import Data.Foldable
takeAnyMVar :: NonEmpty (MVar a) -> IO (MVar a, a)
takeAnyMVar = runConcurrently . foldr1 (<|>) . fmap (Concurrently . takeMVar')
where
takeMVar' mvar = takeMVar mvar >>= \val -> return (mvar, val)
This is inefficient because it has to start a new thread for everyone MVar
on the list.
This is incorrect because multiple threads can take them MVar
and leave it empty before they can be canceled by the operator (<|>)
(which calls at the end race
). One of them will succeed and return its result, the rest will discard their results but leave them MVar
blank.
On Windows, there is a WaitForMultipleObjects function that lets you wait for multiple wait handles. I suspect there is something similar in other operating systems.
Given what MVar
is probably implemented in terms of OS primitives, it should be possible to write a function with the above semantics. But perhaps you need implementation access for this MVar
.
The same applies to the Chan
, QSem
, MSem
and other primitives concurrency.
source to share
If your function is a single consumer (and only runs on one thread), I guess with base 4.8 you can use readMVar
on threads and empty only the return one TVar
, leaving the rest untouched.
As @DanielWagner suggested STM
it would be much simpler.
import qualified Data.Foldable as F
import Data.List.NonEmpty
import Control.Concurrent.STM.TMVar
import Control.Monad
import Control.Monad.STM
takeAnyMVar :: NonEmpty (TMVar a) -> STM (TMVar a, a)
takeAnyMVar = F.foldr1 orElse . fmap (\t -> liftM ((,) t) $ takeTMVar t)
Here we just try takeTMVar
each one in combination with orElse
. If everything is empty, it STM
will be smart enough to wait for one of them to be full and then restart the transaction.
source to share