了解 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 的工作原理,您应该记录比 countcount2 这两个计数器更多的信息。您还应该为每个线程设置一个计数器,使用每个线程的 ConcurrentDictionary<int, int> with keys the ID