是否写入MutableByteArray原子?

时间:2014-03-08 04:20:50

标签: arrays haskell ghc

我正在使用primitive package,我想确保来自一个线程的写入(特别是类型比一个单词更广泛)不能被视为来自另一个线程的垃圾。这有时被称为“撕裂”。

1 个答案:

答案 0 :(得分:1)

我猜这是未定义的行为,无论如何多字写入都不是原子的,正如这个快速而肮脏的程序所证明的那样:

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
module Main where
import Data.Primitive.Types
import Data.Primitive.ByteArray
import Control.Concurrent
import Control.Concurrent.STM
import Control.Applicative
import Control.Monad
import GHC.Prim
main = do
    arr <- newByteArray 16  -- on 64-bit, this is enough for two Ints (8 each)
    inp <- newTVarIO (0::Int)
    o1 <- newEmptyTMVarIO
    o2 <- newEmptyTMVarIO
    let writer last = do
            val <- atomically $ do
                    x <- readTVar inp
                    x <$ check (x > last)
            let v' = (val,val+1)
            writeByteArray arr 0 v'
            atomically $ putTMVar o1 ()
            writer val
        reader last = do
            val <- atomically $ do
                    x <- readTVar inp
                    x <$ check (x > last)
            rVal <- readByteArray arr 0 :: IO (Int,Int)
            let v1 = (val,val+1)
                v0 = (val-1,val)
            when (not $ rVal `elem` [v0,v1,(0,0)]) $ error $ show (val, "got:", rVal)
            atomically $ putTMVar o2 ()
            reader val
    let control :: Int -> IO ()
        control !n = do
            atomically $ writeTVar inp n
            mapM_ (atomically . takeTMVar) [o1,o2]
            when (n<100000) $ control (n+1)
    forkIO $ writer 0
    forkIO $ reader 0
    control 1
    print "done!"

instance Prim (Int,Int) where
    sizeOf# _ = 2# *# (sizeOf# (undefined :: Int))
    alignment# _ = alignment# ( undefined :: Int)
    readByteArray# arr n s = case readByteArray# arr (2# *# n) s of
        (#s',i1 #) -> case readByteArray# arr ((2# *# n) +# 1#) s of
          (#s'2,i2 #) -> (#s'2,(i1,i2)#)
    writeByteArray# arr n (i1,i2) s = case writeByteArray# arr (2# *# n) i1 s of
          s' -> writeByteArray# arr ((2# *# n) +# 1#) i2 s'

使用ghc-7.6.3,-O2 -threaded -rtsopts-N3的7次执行中构建此程序我得到了以下结果:

foo: (4,"got:",(3,5))
foo: (59037,"got:",(59036,59038))
foo: "done!"
foo: (92936,"got:",(92936,92936))
foo: (399,"got:",(398,400))
foo: (7196,"got:",(7195,7197))
foo: (11950,"got:",(11950,11950))
只要CPU架构的内存模型能够保证,

单个机器字的读/写可能是原子的。

对此演示的一个反对意见是(Int,Int)的Prim实例是假的。这是什么类型。但是,考虑到可用的原语,我不知道如何更好地实现多字类型。

您需要使用其他一些同步方法来确保多字写入是原子的。一种简单的方法是将数组保持在MVar。或者我的kickchan包可能会有所帮助(至少在灵感方面,如果它没有解决您的使用案例)。