Haskell中的读写器锁定

Haskell中的读写器锁定,haskell,concurrency,locking,readerwriterlock,Haskell,Concurrency,Locking,Readerwriterlock,我正在实现一个在内存中保存一些数据的web应用程序。有些请求读取此数据进行处理,有些请求更新此数据 在这种情况下,多个读卡器可以同时对数据进行操作,但写入器需要独占访问内存中的数据。我想实现一个解决这个问题的方法。我还需要公平性属性,即锁上的等待程序是以FIFO方式处理的,以避免读写饥饿 Haskell标准库似乎不提供此类功能。我发现它提供了这个功能,但是这个库似乎没有维护(并且在LTS3.22之后从stackage中删除了)——而且我不清楚它的公平性属性 在标准haskell库和stackag

我正在实现一个在内存中保存一些数据的web应用程序。有些请求读取此数据进行处理,有些请求更新此数据

在这种情况下,多个读卡器可以同时对数据进行操作,但写入器需要独占访问内存中的数据。我想实现一个解决这个问题的方法。我还需要公平性属性,即锁上的等待程序是以FIFO方式处理的,以避免读写饥饿

Haskell标准库似乎不提供此类功能。我发现它提供了这个功能,但是这个库似乎没有维护(并且在LTS3.22之后从stackage中删除了)——而且我不清楚它的公平性属性

在标准haskell库和stackage中没有读写器锁库,这让我有点惊讶——读写器模式在许多软件中不是很常见吗?或者Haskell中是否有一种完全不同的(可能是无锁的)方法


编辑:更准确地说,在公平性属性上,当写入程序被阻止等待获取锁时,只有在写入程序获取并释放写锁后才允许后续读锁请求-类似于
MVar
s公平性属性-

读写器锁很容易在STM之上实现

data LockState = Writing | Reading Int
type Lock = TVar LockState

startReading :: Lock -> STM ()
startReading lock = do
   s <- readTVar lock
   case s of
      Writing -> retry
      Reading n -> writeTVar (Reading (succ n))


stopReading :: Lock -> STM ()
stopReading lock = do
   s <- readTVar lock
   case s of
      Writing -> error "stopReading: lock in writing state?!"
      Reading n -> writeTVar (Reading (pred n))


startWriting :: Lock -> STM ()
startWriting lock = do
   s <- readTVar lock
   case s of
      Reading 0 -> writeTVar Writing
      _         -> retry

stopWriting :: Lock -> STM ()
stopWriting lock = do
   s <- readTVar lock
   case s of
      Writing -> writeTVar lock (Reading 0)
      _       -> error "stopwriting: lock in non-writing state?!"
data LockState=写入|读取Int
类型锁定=TVar锁定状态
startreding::Lock->STM()
开始踏面锁=do
s重试
读取n->writeTVar(读取(成功))
停止读取::锁定->STM()
停止读取锁定=do
s错误“停止读取:锁定在写入状态?!”
读取n->writeTVar(读取(pred n))
startWriting::Lock->STM()
开始写入锁定=do
s writevar写入
_->重试
停止写入::锁定->STM()
停止写入锁=do
s writeTVar锁(正在读取0)
_->错误“停止写入:锁定在非写入状态?!”
我对上述内容的主要担忧是:1)在我看来,这有点过头了;2)我们仍然无法保证STM的公平性(生动性)

我想可以在
MVar
s上实现类似的库,尽管这会更复杂,尤其是如果我们想保证公平性的话


我会试图避免使用
MVar
s,而是使用信号量,而使用
QSem
来保证FIFO语义。使用这些,你可以实现Dijkstra风格的读写器。

事实上
并发额外的

正如chi所写的,在STM中没有办法保证公平性。但是我们可以在
IO
中使用
STM
。想法是在chi的
LockState
中添加其他状态,表示读卡器无法获取锁:

data LockState = Writing | Reading Int | Waiting
然后,编写器应首先将状态设置为“等待”,然后等待所有读卡器释放锁。请注意,等待应该在单独的
STM
事务中执行,这就是为什么我们不能保证
STM
中的公平性

这是一个示例实现:它不在Hackage上,但您可以提供它(是BSD许可的)

该实现经过优化以最小化唤醒。使用单
TVar
,当锁处于
等待
状态时,每个读卡器释放都会唤醒所有等待获取锁的读卡器。所以我有两个
TVar
s,一个用于锁定状态,另一个用于读卡器数量


补充:这是一个有趣的(相当长的)讨论,我与IRC用户Cale讨论了读写锁实现的陷阱。

最佳解决方案取决于读写器关系,但我认为您只能使用
MVar
解决您的问题

读卡器1、2和3同时运行,当
4 WRITER working开始时,下一个请求等待读卡器1、2和3,但1、2和3可以终止


(本例中输入FIFO的标准输出和处理顺序不准确,但读取或结算的项目数显示了实际顺序)

为什么不控制.Concurrent.MVar
(使用
takeMVar
)?如果多个读卡器都必须接受MVar,则无法同时操作。在我的场景中,多个读卡器可以处理数据,但是一个写卡器需要独占访问。我想既然我们现在有了STM和
TVar
s,就不再需要一些更简单的锁定形式了。您可以使用
TVar
s和STM,或者在
MVar
s上实现锁定。(后者看起来不平凡)@donatello“内存中保存一些数据”我假设你的“内存数据”是不可变的,那么动作就是原子;这是错误的?共享数据可以通过对web应用程序的某些请求(例如内存缓存)进行更新。
MVar
的问题是,只有一个线程可以
takeMVar
,然后在它执行
putMVar
ed之前,其他线程不能访问
MVar
中的数据。当我希望并发读卡器不相互阻塞时,这不是期望的行为。是的,这没有公平性保证,但感谢您的实现。我现在对TVar不太陌生了。谢谢!这就是我需要的答案!感谢您的回答,但我将使用MVar解决方案。
import System.Clock
import Text.Printf
import Control.Monad
import Control.Concurrent
import Control.Concurrent.MVar

t__ :: Int -> String -> IO ()
t__ id msg = do
    TimeSpec s n <- getTime Realtime
    putStrLn $ printf "%3d.%-3d - %d %s" (s `mod` 1000) n id msg

reader :: MVar [Int] -> Int -> IO ()
reader mv id = do
    t__                            id $ "reader waiting"
    xs <- readMVar mv
    t__                            id $ "reader working begin"
    threadDelay (1 * 10^6)
    t__                            id $ "reader working ends, " ++ show (length xs) ++ " items"

writer :: MVar [Int] -> Int -> IO ()
writer mv id = do
    t__                            id $ "WRITER waiting"
    xs <- takeMVar mv
    t__                            id $ "WRITER working begin"
    threadDelay (3 * 10^6)
    t__                            id $ "WRITER working ends, " ++ show (1 + length xs) ++ " items"
    putMVar mv (id: xs)

main = do

    mv <- newMVar []
    forM_ (take 10 $ zipWith (\f id -> forkIO (f mv id)) (cycle [reader, reader, reader, writer]) [1..]) $ \p -> do
        threadDelay (10^5)
        p

    getLine
c:\tmp>mvar.exe +RTS -N20
486.306991300 - 1 reader waiting
486.306991300 - 1 reader working begin
486.416036100 - 2 reader waiting
486.416036100 - 2 reader working begin
486.525191000 - 3 reader waiting
486.525191000 - 3 reader working begin
486.634286500 - 4 WRITER waiting
486.634286500 - 4 WRITER working begin
486.743378400 - 5 reader waiting
486.852406800 - 6 reader waiting
486.961564300 - 7 reader waiting
487.070645900 - 8 WRITER waiting
487.179673900 - 9 reader waiting
487.288845100 - 10 reader waiting
487.320003300 - 1 reader working ends, 0 items
487.429028600 - 2 reader working ends, 0 items
487.538202000 - 3 reader working ends, 0 items
489.642147400 - 10 reader working begin
489.642147400 - 4 WRITER working ends, 1 items
489.642147400 - 5 reader working begin
489.642147400 - 6 reader working begin
489.642147400 - 7 reader working begin
489.642147400 - 8 WRITER working begin
489.642147400 - 9 reader working begin
490.655157400 - 10 reader working ends, 1 items
490.670730800 - 6 reader working ends, 1 items
490.670730800 - 7 reader working ends, 1 items
490.670730800 - 9 reader working ends, 1 items
490.686247400 - 5 reader working ends, 1 items
492.681178800 - 8 WRITER working ends, 2 items