C# 因为有不寻常的行为
我试图将这个基本筛选函数转换为并行函数,这样它就可以利用多个核 然而,当我运行它时,b变量的值似乎随机跳转,甚至根本不改变,特别是对于to的更高值C# 因为有不寻常的行为,c#,math,for-loop,parallel-processing,C#,Math,For Loop,Parallel Processing,我试图将这个基本筛选函数转换为并行函数,这样它就可以利用多个核 然而,当我运行它时,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
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;
}
静态列表筛选(长到)
{
长f=0;
To/=2;
列表素数=新列表();
bool[]Trues=新bool[To];
如果(到>0)
添加(2);
长b=0;
平行。对于(1L,至,a=>
{
b++;
if(真[b])
返回;
f=2*b+1;
加上(f);
对于(长j=f+b;j
发生了什么,如何阻止这种情况发生?
b
是跨线程共享的。如果多个线程同时处理这个糟糕的变量,您希望发生什么
似乎
b
和a
在代码中总是相等的(或相差一个)。使用a
。并同步访问所有其他共享状态(如列表)。欢迎来到多线程的奇妙世界
马上,我就可以看到循环的每个迭代都会执行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;
}
静态列表筛选(长到)
{
To/=2;
列表素数=新列表();
如果(到>0)
添加(2);
平行。对于(1L,至,a=>
{
长f=2*a+1;
加上(f);
});
Primes.Sort();
返回素数;
}
您在这里面临的问题称为竞争条件
,这是当多个CPU核心将同一变量加载到各自的缓存中,处理它,然后将值写回RAM时发生的情况。显然,写回RAM的值在此期间可能已经很旧了(比如当一个内核在被另一个值覆盖之前加载变量)
首先:我不会使用b++
,而是inti=Interlocked.Increment(参考b)代码>取而代之。增量确保没有两个线程试图同时增加相同的值。结果是递增的值,该值将保存到变量i
中。这是非常重要的,因为您需要该值在for循环的每次迭代中保持不变,否则这是不可能的,因为其他线程将递增该变量
接下来是变量f
和a
(定义为For迭代器)。忘记f
,改用a
f = 2 * b + 1; // wrong
a = 2 * b + 1; // correct
最后:System.Collections.Generic.List不是线程安全的,我重复一遍(因为它很重要)。有关更多详细信息,请参阅
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
关键字只接受引用类型变量作为参数,这是因为锁定变量不会阻止另一个线程访问它。相反,您可以将其想象为在引用顶部设置一个标志,以便向其他线程发出信号我正在这里工作,请不要打扰代码>
当然,如果另一个线程试图访问Primes
,而不事先要求锁定它,那么该线程仍然能够访问该变量
不过,您应该已经了解了所有这些内容,因为在第一次学习多线程时,并行素数筛是最常见的初学者练习之一
编辑:
完成上述所有步骤后,程序不应运行异常然而这并不意味着解决方案是正确的,也不意味着您将获得加速,因为您的许多线程将进行重复工作
假设线程a
负责标记3的每一个倍数,而线程n
负责标记9的倍数。当按顺序运行时,当线程n开始处理9的倍数时,它将看到9已经是另一个(素数)的倍数。但是,由于您的代码现在是并行的,不能保证线程n
开始工作时会标记9。更不用说——因为9可能没有标记——可能会被添加到素数列表中
因此,您必须按顺序查找to
的1和平方根之间的所有素数。为什么将的平方根设置为?这是你必须自己去发现的
一旦找到了从1到到
的平方根的所有素数,就可以使用以前找到的所有素数启动并行素数筛选,以找到其余的素数
最后值得注意的一点是,Primes
只应在Trues
填充后构建。那是因为:
1。您的线程只需处理到
的平方根的2的倍数,因此在当前实现中,不会在根之外向素数添加更多元素
2。如果您选择让线程超出根,您将面临一个问题,即在另一个线程将该数字标记为非素数之前不久,您的一个线程将向素数添加一个非素数