【问题标题】:My multithread creates or overwrites an extra/existing thread我的多线程创建或覆盖额外/现有线程
【发布时间】:2015-12-04 18:36:33
【问题描述】:

我想将测试分布在多个线程上,因此首先我将要测试的总数除以我想要的线程数量(包括余数)。然后我将测试范围内的数字分配给每个线程。每次测试结果为阳性时,计数器都会增加。我在 0 到 123 的范围内对其进行测试,因为我知道结果应该是什么,但是每当我为任务分配超过 1 个线程时,我都会得到错误的结果。调试时,我注意到线程启动后,当前行跳回分配新线程。我不明白为什么。我有一个锁保护柜台,据我所知,它工作正常。下面是对应的代码:

for (int n = 0; n < remainder; n++)
{
      workers[n] = new Thread(() => CountNumbers(startvalue + n * (tasks + 1), startvalue + (n + 1) * (tasks + 1), modulus));
}
for (int m = remainder; m < nrthreads; m++)
{
      workers[m] = new Thread(() => CountNumbers(startvalue + remainder * (tasks + 1) + (m - remainder) * tasks, startvalue + remainder * (tasks + 1) + (m - remainder + 1) * tasks, modulus));
}
for (int k = 0; k < nrthreads; k++)
{
      workers[k].Start();
}
for (int k = 0; k < nrthreads; k++)
{
      workers[k].Join();
}

workers[k].Start() 完成所有 k 时,就会出现“问题”,然后它出于某种原因覆盖了workers 中的最后一个线程。

我对 C# 比较陌生,所以我很难找到缺陷。这是给学校的,所以正确方向的提示可能比干净的答案更合适。

【问题讨论】:

  • 您是否考虑过使用 Parallels.For 或 ForEach 循环而不是自己完成所有“艰苦”的工作?
  • 我没有,主要是因为我对自己的数学部分有信心,而对可用的更高级循环则不太有信心。我想它会清理一下代码。

标签: c# .net multithreading


【解决方案1】:

这是 C# 并行编程中的常见错误。当您通过 lambda 表达式声明匿名函数时,它们会捕获它们引用的任何 变量(不是值)。在您的情况下,您的所有线程都在为 nm 计数器捕获相同的变量实例,从而导致它们的所有执行都看到它的最后一个值。

解决此问题的一种简单方法是在循环范围内声明另一个变量,并将计数器复制到其中。由于范围仅限于循环,因此变量不会在您的线程之间共享。

for (int nOuter = 0; nOuter < remainder; nOuter++)
{
    int n = nOuter;
    workers[n] = new Thread(() => CountNumbers(startvalue + n * (tasks + 1), startvalue + (n + 1) * (tasks + 1), modulus));
}
for (int mOuter = remainder; mOuter < nrthreads; mOuter++)
{
    int m = mOuter;
    workers[m] = new Thread(() => CountNumbers(startvalue + remainder * (tasks + 1) + (m - remainder) * tasks, startvalue + remainder * (tasks + 1) + (m - remainder + 1) * tasks, modulus));
}

编辑:如果您改用 PLINQ 或 TPL 结构,您可以简化代码。以下应该等同于您的整个逻辑:

Parallel.For(0, nrthreads, k =>
{
    if (k < remainder)
        CountNumbers(startvalue + k * (tasks + 1), startvalue + (k + 1) * (tasks + 1), modulus);
    else
        CountNumbers(startvalue + remainder * (tasks + 1) + (k - remainder) * tasks, startvalue + remainder * (tasks + 1) + (k - remainder + 1) * tasks, modulus);
});

【讨论】:

  • 是的,这行得通。我碰巧遇到过很多这样的问题,因为我习惯了 Haskell,而这个问题实际上从未出现过。必须习惯共享变量。
  • 这是一个不幸的陷阱。 Eric Lippert 在Closing over the loop variable considered harmful 上写了一篇博客。 Microsoft 修复了 C#5 中 foreach 循环的行为,但未修复 for 循环的行为。
【解决方案2】:

您因为不同步而错过了某件事的锁定。在我只迭代一个列表并且我有一个有效的索引值(使用 list.Count 时不可能超出范围)之前,我遇到过同样的问题,但确实如此。因为我没有合适的锁。
问题可能出在您的 CountNumbers 方法中。

【讨论】:

    猜你喜欢
    • 2021-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多