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.

+3


source to share


1 answer


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.

+3


source







All Articles