多线程时计算的每次运行的总和不同

时间:2018-02-27 16:11:08

标签: c# .net multithreading

我有一个案例,我在使用多线程时看到每次运行的计算结果都不同。我在Visual Studio 2017中创建了单元测试,演示了该问题(Windows 10上的.NET 4.7.1):

    [TestMethod, TestCategory("Simulator Search")]
    public void MultiThreadTest ()
    {
        Random rand = new Random(1701);
        List<Double> list = new List<Double>();
        for (Int32 i = 0; i < 1000000; ++i)
            list.Add(rand.NextDouble());

        Double singlethreadsum = list.Sum();
        Assert.AreEqual(500164.78615913482, singlethreadsum, "Single thread sum is different");

        Object lockobj = new Object();

        Double multithreadsum1 = 0.0;
        System.Threading.Tasks.Parallel.ForEach(list, new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = 1 }, x =>
        {
            lock (lockobj)
                multithreadsum1 += x;
        });
        Assert.AreEqual(500164.78615913482, multithreadsum1, "Multithread thread sum (max parallelism=1) is different");

        Double multithreadsum2 = 0.0;
        System.Threading.Tasks.Parallel.ForEach(list, x =>
        {
            lock (lockobj)
                multithreadsum2 += x;
        });
        Assert.AreEqual(500164.78615913482, multithreadsum2, "Multithread thread sum is different");
    }

这不会为multithreadsum2产生一致的结果。我知道双精度数字不是精确的表示,这是由于四舍五入的问题。我想知道是否有人知道如何缓解这个问题?请记住,这是一个简化的示例 - 真正的代码需要多线程,因此解决这个问题不是解决方案。

1 个答案:

答案 0 :(得分:3)

问题是在多线程情况下,求和的顺序不同。在两个double个数的总和中,由于精度误差,结果的一小部分可能会丢失。如果您以相同的顺序添加数字,则此具体损失将始终相同。但是如果你有效地随机化了求和的顺序,那么实际损失将随着程序的每次运行而不同。

正如一个指示,尝试以相反的顺序对所有数字求和 - 我的赌注是结果会再次不同。

如果这种效果有重要区别,那么你将不得不使用非平凡的算术方法来处理它。否则,通常的方法是将某些精度作为目标,例如precision=1e-6,并考虑两个double个数,只要它们的绝对差值小于precision

在相关的说明中,极端情况,其中求和错误可能会累积并变得太大。特别是,当添加非常小和非常大的数字时,少数甚至可能完全丢失。在这种情况下,有一种算法可以节省精度,它是这样的:

  1. 对数字列表进行排序
  2. 如果列表中包含的元素不超过一个,则表示您已完成
  3. 删除两个最小的数字并总结
  4. 将总和放入列表中以便保持排序
  5. 重复步骤2-4
  6. 当应用该算法时,总是在具有相似性的数字之间进行求和,因此精度损失会更小。