我想要一个功能
takeAnyMVar :: NonEmpty (MVar a) -> IO (MVar a, a)
同时等待多个MVar
并返回可用的第一个MVar
(及其值)。
特别是,它应该只导致输入列表中的MVar
个中的一个处于空状态,之前是非空的。
我有一个实现,但它既低效又不正确:
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)
这是低效的,因为它必须为列表中的每个MVar
启动一个新线程。
这是不正确的,因为多个线程可能会使用MVar
并将其保留为空状态,然后才能被(<|>)
运算符取消(最后调用race
) 。其中一个将成功并返回其结果,其他人将丢弃他们的结果,但将MVar
留空。
在Windows上,有WaitForMultipleObjects函数,它允许等待多个等待句柄。我怀疑在其他操作系统中有类似的东西。
鉴于MVar
可能是根据OS原语实现的,应该可以使用上述语义编写函数。但是,为了做到这一点,您可能需要访问MVar
的实现。
这同样适用于Chan
,QSem
,MSem
和其他并发原语。
答案 0 :(得分:3)
如果你的函数是唯一的消费者(并且只在一个线程中运行),我想用 base-4.8 你可以在线程中使用readMVar
并且只清空{{ 1}}正在返回,保持其他人不受影响。
根据@DanielWagner的建议,TVar
会简单得多。
STM
我们只需尝试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)
每个takeTMVar
与orElse
结合使用。如果全部为空,STM
将足够聪明,等待其中一个变满,然后重新启动事务。