【问题标题】:BitArray Thread SafetyBitArray 线程安全
【发布时间】:2012-09-03 03:08:22
【问题描述】:

我正在寻找有关并发写入 System.Collections.BitArray 类的线程安全性的信息。

具体来说,考虑以下人为的例子:

BitArray bits = new BitArray(1000000);

Parallel.For(0, bits.Count, i =>
{
    bits[i] = i % 3 == 0;
});

本能告诉我并发解锁访问会产生不正确的结果如果两个线程尝试写入位数组的相同底层整数值,但我找不到任何证据支持它,并且我在运行时没有遇到任何问题。

这样操作安全吗?

如果没有,为什么我没有看到此代码失败或产生不正确的输出?

更新

经过进一步的测试,我认为下面的测试证明在这个例子中使用BitArray不是线程安全的。

另一方面,使用bool[] 似乎是安全的。

private static bool CompareBitArrays(BitArray a, BitArray b)
{
    if (a.Count != b.Count) return false;
    for (int i = 0; i < a.Count; i++)
    {
        if (a[i] != b[i]) return false;
    }
    return true;
}

static void Main(string[] args)
{
    int numElements = 1000000;

    //create single-threaded bitarray with certifiably correct values.
    BitArray controlGroup = new BitArray(numElements);
    for (int i = 0; i < numElements; i++)
    {
        controlGroup[i] = i % 3 == 0;
    }

    //create a BitArray and bool array of equal size and fill them using Parallel.For.
    BitArray bits = new BitArray(numElements);
    bool[] bools = new bool[numElements];

    Parallel.For(0, numElements, i =>
    {
        bits[i] = bools[i] = i % 3 == 0;
    });

    //Create a BitArray from the bool array
    BitArray boolBits = new BitArray(bools);

    //Check if they contain correct values
    bool isBitArrayCorrect = CompareBitArrays(controlGroup, bits); //FALSE
    bool isBoolArrayCorrect = CompareBitArrays(controlGroup, boolBits); //TRUE
}

正如我所提到的,我怀疑原因是 BitArray 中的 32 个值共享数组的相同整数值。

这个逻辑正确吗?

为了提问,请假设除了代码中显示的线程之外没有线程正在访问该集合。

【问题讨论】:

    标签: c# parallel-processing thread-safety bitarray


    【解决方案1】:

    我编写了一个 ThreadSafeBitArray 类,它比 BitArray 类上的常规锁执行得更好。也许有人会觉得它有用。

    public class ThreadSafeBitArray
    {
        private static int[] _cachedShifts;
    
        static ThreadSafeBitArray()
        {
            _cachedShifts = new int[32];
    
            for (int index = 0; index < 32; index++)
            {
                _cachedShifts[index] = ((int)1 << index);
            }
        }
    
        private int _length;
        private int[] _arr;
    
        public ThreadSafeBitArray(int length)
        {
            _length = length;
            _arr = new int[ToUnderlineLength(length)];
        }
    
        private int ToUnderlineLength(int length)
        {
            int underlineLength = length / 32;
    
            if (length % 32 != 0)
            {
                underlineLength++;
            }
    
            return underlineLength;
        }
    
        public int Length
        {
            get { return _length; }
        }
    
        public bool this[int index]
        {
            get
            {
                return (Interlocked.CompareExchange(ref _arr[index >> 5], 0, 0) & (_cachedShifts[index & 31])) != 0;
            }
            set
            {
                int prevValue;
    
                if (value)
                {
                    do
                    {
                        prevValue = Interlocked.CompareExchange(ref _arr[index >> 5], 0, 0);
                    }
                    while (Interlocked.CompareExchange(ref _arr[index >> 5], prevValue | (_cachedShifts[index & 31]), prevValue) != prevValue);
                }
                else
                {
                    do
                    {
                        prevValue = Interlocked.CompareExchange(ref _arr[index >> 5], 0, 0);
                    }
                    while (Interlocked.CompareExchange(ref _arr[index >> 5], prevValue & ~(_cachedShifts[index & 31]), prevValue) != prevValue);
                }
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      查看BitArray.Set方法代码:

      public void Set(int index, bool value)
      {
          if (index < 0 || index >= this.Length)
          {
              throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
          }
          if (value)
          {
              this.m_array[index / 32] |= 1 << index % 32;
          }
          else
          {
              this.m_array[index / 32] &= ~(1 << index % 32);
          }
          this._version++; // this is definitely thread-unsafe
      }
      

      就您通过索引访问集合成员而言,没有枚举它,我在那里看到的唯一线程不安全的代码行是最后一行this._version++;

      但它就在那里,所以,你可以认为这段代码是线程不安全的。

      【讨论】:

      • 你不会说位更改方法不安全吗?请参阅有问题的更新测试。你如何解释结果?
      • @Rotem:位更改方法BitArray.Set 不安全,因为它使用共享状态(_version 字段)。这是我的帖子。如何解释结果?
      • 我问是因为在你的回答中你写的唯一不安全的部分是_version++
      • @Rotem:没错。因为没有_version++ BitArray.Set 只是您的bools[i] = i % 3 == 0 的包装。
      • |= 或 &= 操作不是原子的,因此无论 _version 是什么都不安全
      【解决方案3】:

      我认为来自 MSDN 的 BitArray 下的这句话应该告诉你你想知道的一切:

      此实现不提供同步(线程安全) BitArray 的包装器。

      通过集合枚举本质上不是线程安全的 程序。即使一个集合是同步的,其他线程也可以 仍然修改集合,这会导致枚举器抛出一个 例外。 为了保证枚举过程中的线程安全,你可以 在整个枚举过程中锁定集合或捕获 由其他线程所做的更改导致的异常。

      我把重要的部分加粗了。通过集合的任何枚举都不是线程安全的,因此元素的任何更改也不是线程安全的,您应该锁定整个集合或使用线程安全的集合之一。 (虽然我不确定是否存在 BitArray)

      【讨论】:

      • 即使没有对集合进行任何修改,这是否真的相关?即没有删除或添加任何项目。
      • 如果集合没有改变(不可变),我猜你应该不用担心线程安全。
      • @Int3ὰ 据我所知,OP 正在写入 Parallel.For 循环中的集合。 ((i % 3) == 0) 的结果被写入Bit[i] int 集合。所以这是一个修改,需要同步。
      • @Rotem:枚举集合不是线程安全的操作,所以如果你想安全起见,你可以使用线程同步,因为你永远不知道其他线程可能正在做一些事情同时您的收藏。我想除非您绝对确定该集合不会被任何其他线程同时枚举,否则您将不需要同步。
      • @Tony AFAIK 不是枚举。没有使用枚举器。
      猜你喜欢
      • 1970-01-01
      • 2022-11-21
      • 1970-01-01
      • 2021-07-12
      • 2011-03-30
      • 1970-01-01
      • 1970-01-01
      • 2010-11-15
      • 2011-08-01
      相关资源
      最近更新 更多