简体   繁体   中英

Why is this code not thread-safe?

I have the following program:

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() should output 4075 , and in fact, it does... ~80% of the time.

I'm running the following program:

private const int TEST_RUNS = 50;

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

The output is the following:

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

As you can see, the last values are wrong, so I'm guessing Process() is not thread safe at all.

Why is this? I'm using a shared ConcurrentDictionary inside Process() , but isn't ConcurrentDictionary supposed to be thread-safe? How can wrong results be returned by my Process() method?

Sure it's not thread safe

    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;
    }

To fix, run outer for loop sequentially. You code depends on sequential execution of outer for.

ConcurrentDictionary has nothing to do with that, the fact you use it does not make your code thread-safe.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM