了解 Parallel.For 循环和局部变量
Understanding Parallel.For Loop and local variables
我是并行计算的新手,在 C# 中遇到了一些问题 运行 Parallel.For。
我尝试同时访问多个网站,获取 HTML 并将它们注册到多个 SQLite 数据库中。
在我更精确地检查结果之前,一切似乎都很好。
我注意到在一个 0 到 20 的循环中,代码在循环的共享部分输入了 20 次,而在本地部分只输入了 16 次。因此,缺少 4 个结果。
为了理解这个问题,我做了一个只放两个计数器的经历。一个在全局部分,另一个在本地。全局计数的输出是 20,在本地部分是 1!之后我在全局部分返回到本地部分之前睡了 2 秒。在这种情况下,全局计数的输出是 20,而本地部分是 13!你能解释一下我做错了什么吗?
static void ParalellCalc()
{
var tm = new Stopwatch();
tm.Start();
int count = 0;
int count2 = 0;
var parl = Parallel.For<int>(0, 20, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount}, () => 0, (i, state, Enrada) =>
{
count++;
Thread.Sleep(2000);
return Enrada;
},
(x) =>
{
count2++;
}
);
tm.Stop();
Console.WriteLine(tm.Elapsed);
Console.WriteLine("Global: " + count.ToString());
Console.WriteLine("Local: " + count2.ToString());
Console.WriteLine(tm.Elapsed);
tm.Reset();
}
编辑:
我听取了你的建议,并用 Interlocked.Increment 做了同样的例子来增加计数器。产生的结果完全相同。如果我删除 Thread.Sleep(2000) 第二个计数器产生 1 的结果!?如果我不删除产生 16 的结果。第一个计数器在所有情况下显示 20 的值应该是。谁能解释一下?
static void ParalellCalc()
{
var tm = new Stopwatch();
tm.Start();
int count = 0;
int count2 = 0;
var parl = Parallel.For<int>(0, 20, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount}, () => 0, (i, state, Enrada) =>
{
Interlocked.Increment(ref count);
return Enrada;
},
(x) =>
{
Interlocked.Increment(ref count2);
});
tm.Stop();
Console.WriteLine(tm.Elapsed);
Console.WriteLine("Global: " + count.ToString());
Console.WriteLine("Local: " + count2.ToString());
Console.WriteLine(tm.Elapsed);
tm.Reset();
}
Parallel.For 并行执行您传入的操作(不保证,但在这种情况下很可能是真的)。因此,首先要注意的是,您的 parallel.for 中存在竞争条件,可能会从多个线程访问和写入计数器。通过使用锁定机制(例如 lock(obj)
)包围代码的 counter++
部分,您应该能够解决竞争条件。
++
运算符不是线程安全的,因为它不是原子的。 Interlocked.Increment 是线程安全的。 Interlocked.Increment(ref count)
而不是 count++
和 count2
的相同可能会修复计数。
Parallel.For
方法通过将工作负载拆分为多个分区来并行化工作负载。分区数和每个分区的大小由内部试探法确定。您的实验表明,20 个项目的工作负载可以仅拆分为 1 个分区或 16 个分区,具体取决于每个项目的处理持续时间。通过添加 Thread.Sleep(2000)
行,您可以将工作负载从极轻更改为非常重,因此会创建更多分区来平衡工作负载。这 16 个分区通常由少于 16 个线程处理,因为每个线程处理多个分区。
为了更好地理解 Parallel.For
的工作原理,您应该记录比 count
和 count2
这两个计数器更多的信息。您还应该为每个线程设置一个计数器,使用每个线程的 ConcurrentDictionary<int, int>
with keys the ID。
我是并行计算的新手,在 C# 中遇到了一些问题 运行 Parallel.For。 我尝试同时访问多个网站,获取 HTML 并将它们注册到多个 SQLite 数据库中。 在我更精确地检查结果之前,一切似乎都很好。 我注意到在一个 0 到 20 的循环中,代码在循环的共享部分输入了 20 次,而在本地部分只输入了 16 次。因此,缺少 4 个结果。 为了理解这个问题,我做了一个只放两个计数器的经历。一个在全局部分,另一个在本地。全局计数的输出是 20,在本地部分是 1!之后我在全局部分返回到本地部分之前睡了 2 秒。在这种情况下,全局计数的输出是 20,而本地部分是 13!你能解释一下我做错了什么吗?
static void ParalellCalc()
{
var tm = new Stopwatch();
tm.Start();
int count = 0;
int count2 = 0;
var parl = Parallel.For<int>(0, 20, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount}, () => 0, (i, state, Enrada) =>
{
count++;
Thread.Sleep(2000);
return Enrada;
},
(x) =>
{
count2++;
}
);
tm.Stop();
Console.WriteLine(tm.Elapsed);
Console.WriteLine("Global: " + count.ToString());
Console.WriteLine("Local: " + count2.ToString());
Console.WriteLine(tm.Elapsed);
tm.Reset();
}
编辑: 我听取了你的建议,并用 Interlocked.Increment 做了同样的例子来增加计数器。产生的结果完全相同。如果我删除 Thread.Sleep(2000) 第二个计数器产生 1 的结果!?如果我不删除产生 16 的结果。第一个计数器在所有情况下显示 20 的值应该是。谁能解释一下?
static void ParalellCalc()
{
var tm = new Stopwatch();
tm.Start();
int count = 0;
int count2 = 0;
var parl = Parallel.For<int>(0, 20, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount}, () => 0, (i, state, Enrada) =>
{
Interlocked.Increment(ref count);
return Enrada;
},
(x) =>
{
Interlocked.Increment(ref count2);
});
tm.Stop();
Console.WriteLine(tm.Elapsed);
Console.WriteLine("Global: " + count.ToString());
Console.WriteLine("Local: " + count2.ToString());
Console.WriteLine(tm.Elapsed);
tm.Reset();
}
Parallel.For 并行执行您传入的操作(不保证,但在这种情况下很可能是真的)。因此,首先要注意的是,您的 parallel.for 中存在竞争条件,可能会从多个线程访问和写入计数器。通过使用锁定机制(例如 lock(obj)
)包围代码的 counter++
部分,您应该能够解决竞争条件。
++
运算符不是线程安全的,因为它不是原子的。 Interlocked.Increment 是线程安全的。 Interlocked.Increment(ref count)
而不是 count++
和 count2
的相同可能会修复计数。
Parallel.For
方法通过将工作负载拆分为多个分区来并行化工作负载。分区数和每个分区的大小由内部试探法确定。您的实验表明,20 个项目的工作负载可以仅拆分为 1 个分区或 16 个分区,具体取决于每个项目的处理持续时间。通过添加 Thread.Sleep(2000)
行,您可以将工作负载从极轻更改为非常重,因此会创建更多分区来平衡工作负载。这 16 个分区通常由少于 16 个线程处理,因为每个线程处理多个分区。
为了更好地理解 Parallel.For
的工作原理,您应该记录比 count
和 count2
这两个计数器更多的信息。您还应该为每个线程设置一个计数器,使用每个线程的 ConcurrentDictionary<int, int>
with keys the ID。