【问题标题】:Using pipes/proxies with concurrent MVars将管道/代理与并发 MVar 一起使用
【发布时间】:2013-02-06 14:44:21
【问题描述】:

pipes-3.1.0包的Control.Proxy教程中,作者提供了这个功能:

cache :: (Proxy p, Ord key) => key -> p key val key val IO r
cache = runIdentityK (loop M.empty) where
    loop _map key = case M.lookup key _map of
        Nothing -> do
            val  <- request key
            key2 <- respond val
            loop (M.insert key val _map) key2
        Just val -> do
            lift $ putStrLn "Used cache!"
            key2 <- respond val
            loop _map key2

因为我想有一个并发的应用缓存请求,所以我有以下数据类型

newtype Cache k v = Cache (MVar (M.Map k v))

现在我想要一个带有签名的新 cache 函数

cache :: (Proxy p, Ord k) => Cache k v -> k -> p k v k v IO r
cache (Cache c) k = readMVar c >>= \m -> runIdentityK $ loop m k
    where loop m key = case M.lookup key m of
            Nothing -> do
                val <- request key
                respond val >>= loop (M.insert key val m)
            Just val -> respond val >>= loop m

但是,这无法进行类型检查,因为 readMVarIO monad 中,而 runIdentityKProxy p =&gt; p k v k v IO r monad 中。当然,我可以将 readMVar 提升到这个代理单子中,因为它是 IO 上的转换器,但我找不到合适的组合器。

【问题讨论】:

  • 与 Chris Dueck 的版本相比,Petr 版本的死锁也更安全!想象一下request key 也尝试访问同一个缓存。

标签: haskell


【解决方案1】:

解决方案是一个简单的lift。我之前曾想过使用它,但显然没有足够努力。这是我想要的cache的粗略类型检查版本@

cache = runIdentityK . loop
    where loop (Cache c) key = lift (takeMVar c) >>= \m -> case M.lookup key m of
            Nothing -> do
                val <- request key
                lift . putMVar c $ M.insert key val m
                respond val >>= loop (Cache c)
            Just val -> do
               lift $ putMVar c m 
               respond val >>= loop (Cache c)

【讨论】:

  • 当出现异常时,这看起来很不安全。 take & put 应替换为 withMVar。
  • withMVar 不起作用,因为他需要在获取和释放之间执行管道操作,但他可以使用来自管道安全的括号来在遇到异常时安全地获取和释放它。
【解决方案2】:

就像添加lift 一样简单。但是,您的实现似乎没有达到您的预期。您在开始时只阅读了一次MVar,然后再也不会使用它,只需在循环中传递更新的地图。如果不同的线程应该通过MVar 看到更改,您也必须更新它。一个建议(编译,但我没有测试它是如何工作的):

cache :: (Proxy p, Ord k) => Cache k v -> k -> p k v k v IO r
cache (Cache c) k = runIdentityK loop k
    where 
      loop key = do
        m <- lift (readMVar c)
        case M.lookup key m of
            Nothing -> do
                val <- request key
                lift $ modifyMVar_ c (return . M.insert key val)
                respond val >>= loop
            Just val -> respond val >>= loop

【讨论】:

  • Chris 的版​​本可能更好,因为它通过使用 takeMVar/putMVar 避免了可能的竞争条件(尽管对于缓存来说这可能不是问题)。
  • 请注意 Petr 的评论:竞争条件是两个线程调用 readMVar 然后两个线程调用 modifyMVar。如果使用相同的key 完成,那么request 将被制作两次而不是一次。这两个插入应该是相同的val,这样才不会损害正确性。
猜你喜欢
  • 1970-01-01
  • 2020-10-21
  • 1970-01-01
  • 1970-01-01
  • 2021-10-18
  • 2018-01-29
  • 2013-07-29
  • 1970-01-01
相关资源
最近更新 更多