如何在Haskell中的TChan上限制生产者/消费者情境中的生产者?

时间:2011-03-11 19:51:26

标签: haskell

我们在TChan上有一些倾销值,然后消费者处理这些值。但是消费者无法跟上,所以我们获得了大量的内存使用,因为制作人在频道上倾销了很多东西,但消费者却没有跟上。如果通道队列变成某个特定大小或某种东西,是否有一种直接的方法来生成生产者块,这样我们可以让生产者等待消费者赶上?

4 个答案:

答案 0 :(得分:3)

就像John的回答一样,我建议你自己建立一个有界的TChan。我的代码不同,因为它:

  1. 添加抽象(使BTChan成为ADT)
  2. 由于他在IO中读取当前大小而删除了角落案例。
  3. 尝试在阅读时不要在TVar大小中构建thunk(在写入时不太重要,因为thunk只能是“一个深度” - 下一个操作总是需要评估大小)。
  4. 现在正在讨价还价:http://hackage.haskell.org/package/bounded-tchan
  5. 注意:老实说,如果我是你,我会忽略所有这些答案,只需要在他的评论中使用链接的代码(除非事实证明是错误的代码)。我敢打赌它和我在这里做的一样,但更多的想法。

    {-# LANGUAGE BangPatterns #-}
    module BTChan
            ( BTChan
            , newBTChanIO
            , newBTChan
            , writeBTChan
            , readBTChan
            ) where
    
    import Control.Concurrent.STM
    
    data BTChan a = BTChan {-# UNPACK #-} !Int (TChan a) (TVar  Int)
    
    -- | `newBTChan m` make a new bounded TChan of max size `m`
    newBTChanIO :: Int -> IO (BTChan a)
    newBTChanIO m = do
        szTV <- newTVarIO 0
        c    <- newTChanIO
        return (BTChan m c szTV)
    
    newBTChan :: Int -> STM (BTChan a)
    newBTChan m 
            | m < 1 = error "BTChan's can not have a maximum <= 0!"
            | otherwise = do
            szTV <- newTVar 0
            c    <- newTChan
            return (BTChan m c szTV)
    
    writeBTChan :: BTChan a -> a -> STM ()
    writeBTChan (BTChan mx c szTV) x = do
            sz <- readTVar szTV
            if sz >= mx then retry else writeTVar szTV (sz + 1) >> writeTChan c x
    
    readBTChan :: BTChan a -> STM a
    readBTChan (BTChan _ c szTV) = do
            x <- readTChan c
            sz <- readTVar szTV
            let !sz' = sz - 1
            writeTVar szTV sz'
            return x
    
    sizeOfBTChan :: BTChan a -> STM Int
    sizeOfBTChan (BTChan _ _ sTV) = readTVar sTV
    

    STM程序员需要注意的一些事项:

    • 明确调用retry会产生,将您的haskell线程置于阻塞状态,等待TVarTChan之一的状态更改,以便它可以重试。这是您避免在IO中使用yield函数检查值的方法。
    • 与MVars一样,TVars可以引用thunks,这通常不是你想要的。也许有人应该制作一个定义STVarSTChanSBTChanBTChan(严格和/或有界的TVars和TChans)的hackage包。
    • 实际上有必要编写newBTChanIO而不是杠杆newBTChan,因为new{TVar,TChan}IO的实现即使在unsafePerformIOatomically也可以使用{{1}}做不到。

    编辑:你可以通过将TVar分成一个用于读者和一个用于作者来实现性能提高2-5倍(取决于你使用的界限),从而减少争用。使用标准验证。改进版本0.2.1已经在讨价还价。

答案 1 :(得分:2)

最简单的解决方案可能是添加一个TVar来表示频道中的元素数量:

type BoundedChan a = (TChan a, TVar Int, Int)

writeBoundedChan :: BoundedChan a -> a -> IO ()
writeBoundedChan bc@(tchan, tsz, maxsz) x = do
  cursz' <- readTVarIO tsz
  if cursz' >= maxsz
    then yield >> writeBoundedChan bc x
    else atomically $ do
      writeTChan tchan a
      cursz <- readTVar tsz
      writeTVar tsz (cursz+1)

readBoundedChan :: BoundedChan a -> IO a
readBoundedChan (tchan, tsz, maxsz) = atomically $ do
  x <- readTChan tchan
  cursz <- readTVar tsz
  writeTVar tsz (cursz-1)
  return x

请注意,如果您有多个生成器,则可以稍微超出最大大小,因为cursz值可以在两次读取之间更改。

答案 2 :(得分:1)

我知道游戏有点晚了,但你可以选择实现一个跳过频道,它允许对频道进行非阻塞写入,但是“覆盖”没有的旧值。被任何读者都看到了。

import Control.Concurrent.MVar

data SkipChan a = SkipChan (MVar (a, [MVar ()])) (MVar ())

newSkipChan :: IO (SkipChan a)
newSkipChan = do
    sem <- newEmptyMVar
    main <- newMVar (undefined, [sem])
    return (SkipChan main sem)

putSkipChan :: SkipChan a -> a -> IO ()
putSkipChan (SkipChan main _) v = do
    (_, sems) <- takeMVar main
    putMVar main (v, [])
    mapM_ (\sem -> putMVar sem ()) sems

getSkipChan :: SkipChan a -> IO a
getSkipChan (SkipChan main sem) = do
    takeMVar sem
    (v, sems) <- takeMVar main
    putMVar main (v, sem:sems)
    return v

dupSkipChan :: SkipChan a -> IO (SkipChan a)
dupSkipChan (SkipChan main _) = do
    sem <- newEmptyMVar
    (v, sems) <- takeMVar main
    putMVar main (v, sem:sems)
    return (SkipChan main sem)

答案 3 :(得分:0)

hackage有BoundedChan,但它使用的是MVars,而不是STM。您可以使用它来学习如何编写自己的 - 它只是一页代码。