为什么这段代码不是线程安全的?

时间:2015-09-16 18:37:51

标签: c# multithreading concurrency task-parallel-library

我有以下程序:

private const int TRIANGLE_SIZE = 101;
private const int LIMIT = 1000000;


/*
*    [key] -> [value]
*    [0] -> [1, 0, 0, 0, 0, 0, ...]   First level
*    [1] -> [1, 1, 0, 0, 0, 0  ...]   Second level
*    [2] -> [1, 0, 1, 0, 0, 0  ...]   Third level
*    [3] -> [1, 0, 0, 1, 0, 0  ...]   Fourth level
*    [4] -> [1, 0, 0, 0, 1, 0  ...]   Fifth level
*    ...
*    ...
*
*    Like a matrix, with TRIANGLE_SIZE dimension
*/
private static ConcurrentDictionary<int, int[]> InitPascalTriangle()
{
    ConcurrentDictionary<int, int[]> pascalTriangle = new ConcurrentDictionary<int, int[]>();
    Parallel.For(0, TRIANGLE_SIZE, i =>
    {
        int[] level = new int[TRIANGLE_SIZE];
        level[0] = 1;
        level[i] = 1;
        pascalTriangle.TryAdd(i, level);
    });
    return pascalTriangle;
}

/*
* Fills the Pascal Triangle and counts the values that were bigger than LIMIT
*/
private static int Process()
{
    ConcurrentDictionary<int, int[]> pascalTriangle = InitPascalTriangle();
    int counter = 0;

    Parallel.For(0, TRIANGLE_SIZE, y => Parallel.For(1, y, x =>
    {
        int[] previousLevel = pascalTriangle.GetOrAdd(y - 1, new int[TRIANGLE_SIZE]);
        int value = previousLevel[x] + previousLevel[x - 1];
        pascalTriangle.AddOrUpdate(y, new int[TRIANGLE_SIZE], (k, current) =>
        {
            current[x] = value < LIMIT ? value : LIMIT;
            return current;
        });
        if (value > LIMIT)
            Interlocked.Increment(ref counter);
    }));

    return counter;
}

Process()应输出4075,事实上,它确实...... ~80%的时间。

我正在运行以下程序:

private const int TEST_RUNS = 50;

public static void Main(String[] args)
{
    Parallel.For(0, TEST_RUNS, i => Console.WriteLine(Process()));
    Console.ReadLine();
}

输出如下:

4075     4075     4075     4075    4075
4075     4075     4075     4075    4075
4075     4075     4075     4075    4075
4075     4075     4075     4075    4075
4075     4075     4075     4075    4075
4075     4075     4075     4075    4075
4075     4075     4075     4075    4075
4075     4075     4075     4075    4075
3799     4075     1427     4075    651
1427     681      871      871     871

正如您所看到的,最后的值是错误的,所以我猜Process()根本不是线程安全的。

这是为什么?我在ConcurrentDictionary内部使用共享Process(),但ConcurrentDictionary不应该是线程安全的?我的Process()方法如何返回错误的结果?

1 个答案:

答案 0 :(得分:2)

确定它不是线程安全的

    private static int Process() {
        ConcurrentDictionary<int, IList<int>> pascalTriangle = FillPascalTriangle();
        int counter = 0;

        Parallel.For(0, MAX, x => {
            // x goes from 1 to MAX here, in parallel
            Parallel.For(1, x, y => {
                // y goes from 1 to x here, in parallel
                IList<int> previous;
                // suppose x = 2, you got [1, ...] element here
                pascalTriangle.TryGetValue(x - 1, out previous);
                // now, x = 1 thread (which should calculate [1, ...] elements may or may not be run already 
                // Calculation based on those values it pointless
                int value = previous[y] + previous[y - 1];

                pascalTriangle.AddOrUpdate(x, new List<int>(), (k, current) => {
                    // here, x = 1 thread should update [1, ...] values, but did it already? who knows.
                    current[y] = value < MIN_COMBINATORIC ? value : MIN_COMBINATORIC;
                    return current;
                });

                if (value > MIN_COMBINATORIC)
                    Interlocked.Increment(ref counter);
            });
        });

        return counter;
    }

要修复,请按顺序运行外部循环。您的代码取决于外部for的顺序执行。

ConcurrentDictionary与此无关,您使用它的事实并不会使您的代码成为线程安全的。