如何等待多个'MVar'?

时间:2015-07-01 17:35:42

标签: multithreading haskell

我想要一个功能

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的实现。

这同样适用于ChanQSemMSem和其他并发原语。

1 个答案:

答案 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) 每个takeTMVarorElse结合使用。如果全部为空,STM将足够聪明,等待其中一个变满,然后重新启动事务。