并行函数在 C# 中返回不同的结果

本文关键字:结果 返回 函数 并行 | 更新日期: 2023-09-27 18:35:15

我的Windows表单C#应用程序中有这些代码:

private void button7_Click(object sender, EventArgs e)
{
    ThreadStart starter = delegate { thread_func(2, 1000000); };
    thread1_thread = new Thread(starter);
    starter = delegate { thread_func(1000000, 2000000); };
    thread2_thread = new Thread(starter);
    starter = delegate { thread_func(2000000, 3000000); };
    thread3_thread = new Thread(starter);
    starter = delegate { thread_func(3000000, 4000000); };
    thread4_thread = new Thread(starter);
    thread1_thread.Start();
    thread2_thread.Start();
    thread3_thread.Start();
    thread4_thread.Start();
}
void thread_func(decimal input1,decimal input2)
{
    for (; input1 < input2; input1++)
    {
        threadNumbers_list.Add(input1);
        if (input1 % 2 != 0)
        {
            if (isPrime_func(input1))
            {
                PrimeNumbers_decimal_list.Add(input1);
            }
        }
    }
}
public static Boolean isPrime_func(decimal number)
    {
        decimal boundary = (decimal)Math.Floor(Math.Sqrt((double)number));
        if (number == 1) return false;
        if (number == 2) return true;
        for (decimal i = 2; i <= boundary; ++i)
        {
            if (number % i == 0) return false;
        }
        return true;
    }

每次我运行单击该按钮时,我都会得到不同的结果。我已经尝试了很多事情,但无法弄清楚为什么会发生这种情况。即使对于较低的范围,它也会发生。例如,在 100 个数字的范围内,它总是给出相同的结果。有时我的列表计数达到283138,有时达到283131和其他接近的数字。

另一个奇怪的是,当我评论检查偶数时,操作时间比这种模式短。怎么了?

并行函数在 C# 中返回不同的结果

当多个线程访问一个列表时,该列表必须是线程安全的,否则您将遇到很多问题。

.NET 提供了一些线程安全的集合,如 ConcurrentQueue<T> 类。

旁注:请考虑使用 Tasks 而不是线程。此外,.NET 框架通过 Parallel 类支持数据并行性。请考虑改用此类。

关于不检查数字是否偶数时的性能,我在本地进行了测试,得到了以下数字:

  • 当我不检查数字是否偶数时,需要 ~76 秒。
  • 当我检查数字是否偶数时,需要 ~66 秒。

因此,这与您的测量结果不符。这可能是由您的测量方式引起的。我用这样的Stopwatch来衡量:

//...
Stopwatch sw = Stopwatch.StartNew();
thread1_thread.Start();
thread2_thread.Start();
thread3_thread.Start();
thread4_thread.Start();
thread1_thread.Join();
thread2_thread.Join();
thread3_thread.Join();
thread4_thread.Join();
long result = sw.ElapsedMilliseconds;
//...

顺便说一下,您可以执行以下操作,从而为您节省一些执行时间:

thread_func 方法中的每个线程创建一个普通的 List<T> 实例,以便您不会遇到多线程问题。然后,循环完成后,您可以从本地列表更新主列表。只有更新主列表必须是线程安全的。在这种情况下,我希望主列表是一个普通List<T>,并且您使用 lock 关键字来同步对它的访问,因为您只需要更新它 4 次(线程数)。