Haskell中的读写器锁定
我正在实现一个在内存中保存一些数据的web应用程序。有些请求读取此数据进行处理,有些请求更新此数据 在这种情况下,多个读卡器可以同时对数据进行操作,但写入器需要独占访问内存中的数据。我想实现一个解决这个问题的方法。我还需要公平性属性,即锁上的等待程序是以FIFO方式处理的,以避免读写饥饿 Haskell标准库似乎不提供此类功能。我发现它提供了这个功能,但是这个库似乎没有维护(并且在LTS3.22之后从stackage中删除了)——而且我不清楚它的公平性属性 在标准haskell库和stackage中没有读写器锁库,这让我有点惊讶——读写器模式在许多软件中不是很常见吗?或者Haskell中是否有一种完全不同的(可能是无锁的)方法Haskell中的读写器锁定,haskell,concurrency,locking,readerwriterlock,Haskell,Concurrency,Locking,Readerwriterlock,我正在实现一个在内存中保存一些数据的web应用程序。有些请求读取此数据进行处理,有些请求更新此数据 在这种情况下,多个读卡器可以同时对数据进行操作,但写入器需要独占访问内存中的数据。我想实现一个解决这个问题的方法。我还需要公平性属性,即锁上的等待程序是以FIFO方式处理的,以避免读写饥饿 Haskell标准库似乎不提供此类功能。我发现它提供了这个功能,但是这个库似乎没有维护(并且在LTS3.22之后从stackage中删除了)——而且我不清楚它的公平性属性 在标准haskell库和stackag
编辑:更准确地说,在公平性属性上,当写入程序被阻止等待获取锁时,只有在写入程序获取并释放写锁后才允许后续读锁请求-类似于
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