13

Go 语言有一个select语句,可用于轮询多个通道并根据哪个通道首先非空来执行特定操作。

例如

select {
  case a := <- chanA:
    foo(a)
  case b := <- chanB:
    baz(b)
  case c := <- chanC:
    bar(c)
}

这将等到或非空,然后如果例如chanA非空,它将读取并将结果存储在 中,然后调用。也可以添加一个子句,这意味着该语句不会在通道上等待,而是在所有通道为空时执行该子句的任何内容。chanBchanCchanBchanBbbaz(b)default:selectdefault

TChan在 Haskell 中为 STM 实现这样的最佳方法是什么?它可以通过 if-else 链天真地完成:检查每个 chanisEmptyChan是否为空,如果它不为空,则从中读取并调用适当的函数,或者retry如果所有通道都为空则调用。我想知道是否会有更优雅/惯用的方式来做到这一点?

请注意,Go 的select语句也可以在其 case 中包含 send 语句,并且只有在其通道为空时才会完成一个 send 语句。如果该功能也可以复制,那就太好了,尽管我不确定是否会有一种优雅的方式来做到这一点。

只是稍微相关,但我刚刚注意到,我不确定在哪里发布:在Control.Monad.STM页面的描述中有一个错字retry

“实现可能会阻塞线程,直到它读取的 TVar 之一被更新。”

4

2 回答 2

11

您可以使用orElseselect实现语义(用于读取和写入)(注意:它特定于 ghc。)例如:

forever $ atomically $
  writeTChan chan1 "hello" `orElse` writeTChan chan2 "world" `orElse` ...

这个想法是,当一个动作重试时(例如,您正在写入 chan,但它已满;或者您正在读取 chan,但它是空的),执行第二个动作。该default语句只是return ()链中的最后一个动作。

添加:正如@Dustin 所说,去选择随机分支是有充分理由的。可能最简单的解决方案是在每次迭代中随机播放操作,在大多数情况下应该没问题。正确复制 go 语义(仅对活动分支进行洗牌)有点困难。可能手动检查isEmptyChan所有分支是要走的路。

于 2014-07-07T14:35:29.537 回答
5

避免饥饿

foreverK :: (a -> m a) -> a -> m ()
foreverK loop = go
 where go = loop >=> go

-- Existential, not really required, but feels more like the Go version
data ChanAct = Action (TChan a) (a -> STM ())

perform :: STM ()
perform (Action c a) = readTChan c >>= a

foreverSelectE :: [ChanAct] -> STM ()
foreverSelectE = foreverSelect . map perform

foreverSelect :: [STM ()] -> STM ()
foreverSelect = foreverK $ \xs -> first xs >> return (rotate1 xs)

-- Should only be defined for non-empty sequences, but return () is an okay default.
-- Will NOT block the thread, but might do nothing.
first :: [STM ()] -> STM ()
first = foldr orElse (return ())

-- Should only be defined for non-empty sequences, really.
-- Also, using a list with O(1) viewL and snoc could be better.
rotate1 :: [a] -> [a]
rotate1 []    = []
rotate1 (h:t) = t ++ [h]

example = foreverSelectE
    [ Action chanA foo
    , Action charB baz
    , Action chanC bar
    ]

为了永远避免,您可以改为使用mkSelect :: [STM ()] -> STM (STM ())“隐藏” TVar [STM ()] 并在每次使用时旋转它,如下所示:

example1 :: STM ()
example1 = do
    select <- mkSelect [actions] -- Just set-up
    stuff1
    select -- does one of the actions
    stuff2
    select -- does one of the actions

main = OpenGL.idleCallback $= atomically example1

扩展该技术,您可以选择报告它是否执行了一个动作,或者它执行了哪个动作,甚至循环,直到所有动作都被阻塞,等等。

于 2014-07-08T05:10:06.463 回答