【问题标题】:Why is this code not thread-safe?为什么这段代码不是线程安全的?
【发布时间】:2015-09-16 18:37:50
【问题描述】:

我有以下程序:

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() 根本不是线程安全的。

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

【问题讨论】:

  • 据我所见,您的代码从 pascalTriangle 读取 y-1'st 值并更新 y'th。这会产生竞争条件,因为 y-1st 可能已在另一个线程中更新,也可能尚未更新。
  • @Slava 我知道,但ConcurrentDictionary 不是为解决这个问题而设计的吗?
  • 使用线程安全的类并不能使你自己的代码线程安全。
  • 并发字典允许在没有锁的情况下读取/写入集合。你的算法有缺陷,ConcurrentDictionary 帮不上忙

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


【解决方案1】:

肯定不是线程安全的

    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 循环。您的代码依赖于外部 for 的顺序执行。

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

【讨论】:

    猜你喜欢
    • 2018-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-22
    • 2011-05-18
    相关资源
    最近更新 更多