【发布时间】:2014-05-05 11:42:18
【问题描述】:
我正在尝试实现一个堆栈以在并发应用程序中使用。我想要以下语义:push 永远不应该阻塞,pop 应该阻塞空堆栈上的调用线程,但仍然允许 pushes。我实现如下(底部无关位):
data Stream a = Stream a (MVar (Stream a))
data Stack a = Stack (MVar (Int, MVar (Stream a)))
popStack :: Stack a -> IO a
popStack (Stack stack) = do
(sz, mvar) <- takeMVar stack
mbStream <- tryTakeMVar mvar
case mbStream of
Nothing -> putMVar stack (sz, mvar) >> popStack (Stack stack)
Just (Stream x xs) -> putMVar stack (sz-1, xs) >> return x
如果流MVar 为空,我必须释放堆栈上的锁并重试。然而,这看起来像是一个杂物:如果一个线程在一个空堆栈上调用pop,它可能会在被挂起之前循环几次,即使在该线程正在执行时MVar 不会变满。有没有更好的方法利用MVars 来编写具有所需语义的pop?
import Control.Concurrent.MVar
import Control.Monad
import Control.Concurrent
import Text.Printf
newStack :: IO (Stack a)
newStack = do
stream <- newEmptyMVar
Stack <$> newMVar (0, stream)
pushStack :: Stack a -> a -> IO ()
pushStack (Stack stack) val = do
(sz, stream) <- takeMVar stack
stream' <- newMVar (Stream val stream)
putMVar stack (sz+1, stream')
test = do
s <- newStack
_ <- forkIO $ mapM_ (\a -> printf "pushing %c... " a >> pushStack s a >> threadDelay 100000) ['a' .. 'z']
_ <- forkIO $ do
replicateM 13 (popStack s) >>= printf "\npopped 13 elems: %s\n"
replicateM 13 (popStack s) >>= printf "\npopped 13 elems: %s\n"
threadDelay (5*10^6)
putStrLn "Done"
【问题讨论】:
-
给定的语义不完整。您需要类似“已推送每个弹出结果”之类的内容。尽管如此,您仍无法将堆栈与队列(?)区分开来,因此您可以查看hackage.haskell.org/package/base-4.7.0.0/docs/… 的(源代码)。请注意,那里没有
tryTakeMVar。而且-您绝对想自己进行锁定吗?如果没有,请考虑 STM。 -
@d8d0d65b3f7cf42 为什么我需要将其添加到语义中?如果一个值被弹出,如果它没有被推送,它怎么会到达那里? Chan 是 FIFO,Stack 是相反的 (LIFO) - 它们如何无法区分?如果我能用
Chan写Stack,那就太好了。我见过Chan,我不认为我可以以同样的方式实现Stack- 对于堆栈来说,读写MVars 无论如何都会指向相同的值。我可以使用 STM——但这能解决这个特殊问题吗? -
您的规范仅涉及阻塞。你需要添加一些关于价值观的东西。否则,您的实现可能会凭空制造,或者忽略已推送的内容,或复制它。 FIFO 在并发设置中的含义并不明显。也许你想要一些静态属性(“如果只有一个线程在运行,那么它就是 FIFO”),或者线性化。在我们开始讨论(正确性)实现之前,所有这些都需要明确说明。
-
好的,当你说语义不完整时,我明白你的意思了。 push 应该总是将堆栈上的元素数量增加 1,并且 pop 应该总是将该数量减少 1,除非堆栈为空,在这种情况下它会阻塞。我希望如果我知道 1 在 2 之前被推送,这意味着 2 将在 1 之前弹出。无论如何,我会在继续之前阅读您链接的论文。
-
你不一定有一个明确定义的“之前”关系,或者你需要太多的锁定来强制它。
标签: haskell concurrency