我正在尝试将此主要筛选功能转移到使用Parallel.For,因此它可以使用多个核心。
然而,当我运行它时,b变量的值似乎随机跳跃甚至根本不会改变,尤其是对于更高的To值。
static List<long> Sieve(long To)
{
long f = 0;
To /= 2;
List<long> Primes = new List<long>();
bool[] Trues = new bool[To];
if (To > 0)
Primes.Add(2);
long b = 0;
Parallel.For(1L, To, a =>
{
b++;
if (Trues[b])
return;
f = 2 * b + 1;
Primes.Add(f);
for (long j = f + b; j < To; j += f)
Trues[j] = true;
});
return Primes;
}
发生了什么,我怎么能阻止这种情况发生?
答案 0 :(得分:2)
这里面临的问题叫做race conditions
,当多个CPU核心将相同的变量加载到各自的缓存中,然后处理它,然后将值写回RAM时,会发生什么。显然,写回RAM的值可能在此期间已经过时(例如,当核心在用另一个值覆盖之前加载变量时)
首先:我不会使用b++
而是使用int i = Interlocked.Increment(ref b);
。 Interlocked.Increment确保没有2个线程尝试同时递增相同的值。结果是递增的值将保存到变量i
中。这是非常重要,因为您需要该值在for循环的每次迭代中保持不变,否则这是不可能的,因为其他线程将递增此变量。
接下来是变量f
和a
(定义为For-iterator)。忘记f
,请改用a
。
f = 2 * b + 1; // wrong
a = 2 * b + 1; // correct
最后:System.Collections.Generic.List是 NOT ,我重复(因为它很重要) NOT 线程安全。有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx。
Primes.Add(f); // will likely break something
lock (Primes) // LOCK the list
{
Primes.Add(a); // don't forget, we're using 'a' instead of 'f' now
}
lock
关键字仅接受引用类型变量作为参数,这是因为锁定变量 NOT 会阻止另一个线程访问它。相反,您可以将其想象为在引用顶部设置标志,以便发出其他线程的信号I'm working here, please do not disturb!
当然,如果另一个线程试图访问Primes
而没有要求事先锁定它,那么该线程仍然可以访问该变量。
你应该已经学会了所有这些,因为Parallel Prime Sieve是第一次学习多线程时最常见的初学练习之一。
修改强>
完成上述所有步骤后,程序不应该运行; 然而这并不意味着解决方案是正确的,或者您已经获得了加速,因为您的许多线程将会重复工作。
假设Thread a
有责任标记3的每个倍数,而Thread n
则负责标记9的倍数。当按顺序运行时,Thread n
开始处理9的倍数,它将看到9已经是另一个(素数)的倍数。但是,由于您的代码现在是并行的,因此无保证将在时间Thread n
开始工作时标记9。更不用说 - 因为9可能没有标记 - 可能会被添加到素数列表中。
因此,您必须顺序查找1和To
的平方根之间的所有素数。为什么To
的平方根?这是你必须自己找到的东西。
一旦找到从1到To
的平方根的所有素数,就可以使用之前找到的所有素数来启动平行素数筛,以找到其余的素数。
最后一个值得注意的一点是,Primes
只应在 Trues
填充后才构建。那是因为:
1。您的线程只需将2的倍数处理为To
的平方根,因此在当前实现中不会再向{{1}添加任何元素超越根。
2。如果您选择让线程超越root,那么您将遇到问题,您的某个线程将很快向Primes
添加非素数在另一个线程将该数字标记为非素数之前,这不是您想要的。
3。即使您很幸运且Primes
的所有元素确实都是1到Primes
之间的所有素数,它们可能不一定是有序的,要求To
先排序。
答案 1 :(得分:1)
b
跨线程共享。如果多个线程同时敲响那个糟糕的变量,那么你会发生什么呢?
似乎b
和a
在您的代码中总是相等(或者相差一个)。使用a
。并同步访问所有其他共享状态(如列表)。
答案 2 :(得分:1)
欢迎来到精彩的多线程世界。
马上,我可以看到循环的每次迭代都会b++
,然后在整个过程中使用b
。这意味着循环的每次迭代都将在所有其他迭代中修改b
的值。
您可能想要做的是使用内联函数中提供的a
变量,这与您在b
中尝试的完全相同。如果不是这种情况,那么你应该研究锁定b
并将其值复制到本地(每个迭代)变量,然后再对其进行处理。
试试这个,让我知道你想做什么:
static List<long> Sieve(long To)
{
To /= 2;
List<long> Primes = new List<long>();
if (To > 0)
Primes.Add(2);
Parallel.For(1L, To, a =>
{
long f = 2 * a + 1;
Primes.Add(f);
});
Primes.Sort();
return Primes;
}