我使用简单的API实现了一个线程安全的可变对象存储。直观地说,它具有性能限制,即与多个并发写入器的锁争用。首先,这是API:
new :: String -> IO Int
get :: Int -> IO (Maybe String)
delete :: Int -> IO ()
实施中隐藏的是全局可访问的IORef
,其中包含Ref
的纯数据结构unsafePerformIO
。它是IntMap
,并且是增加的插槽号。将元素添加到IntMap
时,插槽将递增并作为对条目的引用返回。以下是实施:
module MyModule (new,get,delete) where
import Control.Monad (liftM)
import Data.IORef
import qualified Data.IntMap as Map
import Data.Functor ((<$>))
import Data.Maybe
import System.IO.Unsafe (unsafePerformIO)
new :: String -> IO Int
new = atomicModifyIORef ref . createEntry
get :: Int -> IO (Maybe String)
get i = liftM (Map.lookup i) (table <$> readIORef ref)
delete :: Int -> IO ()
delete = atomicModifyIORef ref . deleteEntry
---------------
-- IORef mutation
data Ref = Ref { lastSlot :: !Int , table :: Map.IntMap String }
{-# NOINLINE ref #-}
ref :: IORef Ref
ref = unsafePerformIO $
newIORef Ref { lastSlot = 0, table = Map.empty }
createEntry :: String -> Ref -> (Ref, Int)
createEntry val reg =
ref `seq` (reg', newSlot) where
newSlot = lastSlot reg + 1
reg' = reg { lastSlot = newSlot,
table = Map.insert newSlot val (table reg) }
deleteEntry :: Int -> Ref -> (Ref, ())
deleteEntry slot reg = (reg { table = Map.delete slot (table reg) }, ())
使用的一个例子是:
test :: IO ()
test = do
x <- new "foo"
y <- new "bar"
fromJust <$> get y >>= print -- prints "bar"
fromJust <$> get x >>= print -- prints "foo"
delete x >> delete y
这是线程安全的,例如new
可以在单独的线程中调用。这很有效。正如 Gregory Collins 所指出的,问题是争用。当多个并发写入器的情况下添加了100k +条目时,atomicModifyIORef
会将值与thunk交换出来。然后,当我尝试使用IntMap
下面的IORef
时,多个mutators将尝试强制将值传递给WHNF,导致重复工作或线程阻塞在“黑洞”上,这两种方式都不好
我正在寻找的是另一种实现方式,new
,get
和delete
的类型不受影响。一种可能性是使用锁定条带,以减少锁争用。 snap框架中的This HashMap implementation通过将密钥或散列空间划分为N个分区(即Vector (MVar (HashTable k v))
)来实现锁定条带化,然后使用互斥锁保护每个分区。我不清楚如何使用此HashMap在我的模块中重新实现new
,get
和delete
,而无需修改类型(这会破坏许多使用的模块)它)。
unsafePerformIO
。