我正在使用primitive
package,我想确保来自一个线程的写入(特别是类型比一个单词更广泛)不能被视为来自另一个线程的垃圾。这有时被称为“撕裂”。
答案 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包可能会有所帮助(至少在灵感方面,如果它没有解决您的使用案例)。