3

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

具体来说,考虑以下人为设计的示例:

BitArray bits = new BitArray(1000000);

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

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

这是一个安全的操作吗?

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

更新

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

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

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
}

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

这个逻辑正确吗?

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

4

3 回答 3

2

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

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

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

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

于 2012-09-10T09:13:01.633 回答
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++;

但它就在那里,因此,您可以将此代码视为线程不安全的。

于 2012-09-10T09:32:48.027 回答
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);
            }
        }
    }
}
于 2014-10-12T00:03:35.597 回答