I'd like a function
takeAnyMVar :: NonEmpty (MVar a) -> IO (MVar a, a)
which waits on multiple MVar
s simultaneously and returns the first MVar
(and its value) that becomes available.
In particular, it should only cause a single one of the MVar
s in the input list to be in an empty state that wasn't empty before.
I have an implementation, but it is both 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)
It's inefficient because it has to start a new thread for every MVar
in the list.
It is incorrect, because multiple threads might take their MVar
and leave it in an empty state before they can be cancelled by the (<|>)
operator (which calls race
in the end). One of them will succeed and return its result, the others will discard their results but leave their MVar
s empty.
On Windows, there is the WaitForMultipleObjects function, which allows to wait on multiple wait handles. I suspect there is something similar in other operating systems.
Given that MVar
is probably implemented in terms of OS primitives, it should be possible to write a function with the above sematics. But maybe you need access to the implementation of MVar
in order to do that.
The same applies to Chan
, QSem
, MSem
, and other concurrency primitives.