下面的代码很奇怪,它给了我一个可枚举范围的Sum
。
为什么?
var num = Enumerable.Range(-10000, 20001).Reverse().ToList();
Action task1 = () =>
{
Console.WriteLine($"Sum 2: {num.Sum()}");
};
Action task2 = () =>
{
num.Sort();
Console.WriteLine($"Sum 3: { num.Sum()}");
};
Parallel.Invoke(task1, task2);
Console.ReadLine();
预期输出:
Sum 2: 0
Sum 3: 0
输出:
Sum 2: 94646670
Sum 3: 0
答案 0 :(得分:2)
每当您运行并行任务时,两个任务都可能同时访问同一个内存(假设您没有可以序列化任务的SynchronizationContext,或者lock
来访问门禁)。在这种情况下,一个线程在列表上迭代,而另一个线程在该列表中的元素周围移动。所以总和可能不止一次包含一些元素,而其他元素根本不包含。在某些情况下(不是在这个例子中),线程甚至可能读取元素的 part ,例如如果元素的大小超过原子读取的宽度(在Wintel上对齐32位)。
要证明这是原因,请尝试创建第二个列表并将其提供给第二个任务,如下所示:
var num1 = Enumerable.Range(-10000, 20001).Reverse().ToList();
var num2 = Enumerable.Range(-10000, 20001).Reverse().ToList();
Action task1 = () =>
{
Console.WriteLine("Sum 2: {0}", num1.Sum());
};
Action task2 = () =>
{
num2.Sort();
Console.WriteLine("Sum 3: {0}", num2.Sum());
};
Parallel.Invoke(task1, task2);
如果您这样做,您将始终获得0
两个答案。
答案 1 :(得分:1)
List<T>
不是线程安全的,因此Parallel.Invoke
发生在不同的线程上。你的答案是非确定性的,因为线程调用num.Sort()
正在修改列表而另一个正在枚举它。
答案 2 :(得分:0)
Sort
和Sum
不是原子的...一个线程正在计算总和,而sort正在移动数字,因此Sum
将计算两次数字,并且一些数字将被完全跳过。您会注意到每次运行代码时实际上都会得到不同的结果。
答案 3 :(得分:0)
排序变异状态,因此使用OrderBy(x => x)
而不是Sort()
,您将获得可预测的排序结果。