.Net 中的多个 Parallel.ForEach 循环

Posted

技术标签:

【中文标题】.Net 中的多个 Parallel.ForEach 循环【英文标题】:Multiple Parallel.ForEach loops in .Net 【发布时间】:2021-05-21 11:12:39 【问题描述】:

在.Net 进程中,只有一个托管线程池。我们可以根据需要通过公共属性设置最小和最大线程数。

在 .Net 中,我们还有 Parallel.ForEach,它在后台从这个托管线程池中获取其线程。

Parallel.ForEach中我们也可以设置MaxDegreeOfParallelism来限制最大线程数。

我有两个 Parallel.ForEach 并行运行。一个将MaxDegreeOfParallelism 设置为 3,另一个设置为 7。

我的问题是:我的两个Parallel.ForEach 循环是否都在后台使用相同的线程池。如果是,Parallel.ForEach 如何使用MaxDegreeOfParallelism 限制线程。多少倍 Parallel.ForEach 循环和一个托管线程池一起工作? 如果您能在我进入 .net 核心源代码之前提供高级解释或一些指示,那将非常有帮助。

【问题讨论】:

这个问题并不重要(是的,它是一样的)。 Parallel.ForEach 使用 所有可用内核 来处理大量数据,因此嵌套的 Parallel.ForEach 不会找到任何可用的内核来使用。那不是错误。 Parallel.ForEach 用于数据并行性 - 通过分区处理大量内存数据并使用单独的工作线程/任务来处理每个分区。 你想做什么,为什么你有嵌套的并行循环?要么代码错误,要么使用了错误的构造。例如,Parallel.ForEach 不支持异步操作,因为它对数据并行没有意义。还有其他用于限制并发或异步处理的类,例如 ActionBlock @TheodorZoulias 也无所谓I have two parallel.ForEach running in parrallel。那些仍然会使用他们可以使用的所有核心。也许通过仔细配置可以确保 CPU 没有饱和,但是为什么?这真的是数据并行问题吗?还是滥用Parallel.ForEach 这会以任何方式改变答案吗? Parallel.ForEach 通过工作任务和线程池使用核心。一个或多个线程池,它将尝试尽可能长时间地继续使用其工作任务 - 除非配置为在一段时间后释放任务。所以细节很重要。线程池的数量,不是真的 @ShahryarRazzak 没关系,无论有多少线程池,Parallel.ForEach 都会让内核保持忙碌。您可以将其配置为使用不同的线程池,但这不会释放内核。你想解决什么问题? 【参考方案1】:

我的两个Parallel.ForEach 循环是否在后台使用相同的线程池。

是的

Parallel.ForEach 如何使用 MaxDegreeOfParallelism 限制线程。

ParallelOptions.MaxDegreeOfParallelism 获取或设置thisParallelOptions 实例启用的最大并发任务数。

默认情况下,Parallel 类上的方法会尝试使用所有可用的处理器,这些方法是不可取消的,并且以默认的 TaskScheduler (TaskScheduler.Default) 为目标。 ParallelOptions 可以覆盖这些默认值。

多个Parallel.ForEach 循环和一个托管线程池如何协同工作?

它们共享同一个线程池。正如here所描述的那样:

一般情况下,您不需要修改此设置。但是,您可以选择在高级使用场景中显式设置它,例如:

当您同时运行多个算法并希望手动定义每个算法可以使用多少系统时。您可以为每个设置一个 MaxDegreeOfParallelism 值。

【讨论】:

【参考方案2】:

默认情况下,Parallel.ForEach 循环使用来自ThreadPool 的线程,这是一个静态类,它只有一个per process。可以通过配置ParallelOptionsTaskScheduler 属性来修改此行为。创建一个自定义的TaskScheduler 作为替代ThreadPool 并不是一件容易的事,但也不是火箭科学。如果您有兴趣,可以找到here 一些可以帮助您入门的材料(article)。

现在当两个并行循环同时运行时会发生什么,它们都在ThreadPool 线程上调度工作。如果它们都配置了特定的MaxDegreeOfParallelism,并且两者的总和不超过ThreadPool 按需创建的最小线程数¹,那么这两个循环不会在调度方面相互干扰。当然,在 CPU 资源稀缺的情况下,仍然可以相互竞争 CPU 资源。在这种情况下,操作系统将成为仲裁者。

如果至少有一个并行循环没有配置特定的MaxDegreeOfParallelism,则此选项的有效默认值为-1,这意味着无限并行。这将导致ThreadPool 立即饱和,并保持饱和直到未配置并行循环的源可枚举完成。在此期间,两个并行循环将相互严重干扰,谁将获得饱和ThreadPool 每约 1,000 毫秒注入的额外线程取决于谁先要求它。最重要的是,饱和的ThreadPool 会对在此期间也可能处于活动状态的任何其他独立回调、计时器事件、异步延续等产生负面影响。

如果两个并行循环都配置了,并且两者的总和MaxDegreeOfParallelism超过了可用线程的数量,那么情况与之前类似。唯一的区别是ThreadPool中的线程数会逐渐增加,饱和事件可能会比并行循环的执行更早结束。

以下是演示此行为的示例:

ThreadPool.SetMinThreads(4, 4);
Task[] tasks = new[]  'A', 'B' .Select(name => Task.Run(() =>

    Thread.Sleep(100); if (name == 'B') Thread.Sleep(500);
    Print($"name-Starting");
    var options = new ParallelOptions()  MaxDegreeOfParallelism = 10 ;
    Parallel.ForEach(Enumerable.Range(1, 10), options, item =>
    
        Print($"name-Processing #item");
        Thread.Sleep(1000);
    );
    Print($"name-Finished");
)).ToArray();
Task.WaitAll(tasks);

static void Print(string line)

    Console.WriteLine($@"DateTime.Now:HH:mm:ss.fff [Thread.CurrentThread
        .ManagedThreadId] > line");

输出:

15:34:20.054 [4] > A-Starting
15:34:20.133 [6] > A-Processing #2
15:34:20.133 [7] > A-Processing #3
15:34:20.133 [4] > A-Processing #1
15:34:20.552 [5] > B-Starting
15:34:20.553 [5] > B-Processing #1
15:34:20.956 [8] > A-Processing #4
15:34:21.133 [4] > A-Processing #5
15:34:21.133 [7] > A-Processing #6
15:34:21.133 [6] > A-Processing #7
15:34:21.553 [5] > B-Processing #2
15:34:21.957 [8] > A-Processing #8
15:34:21.957 [9] > A-Processing #9
15:34:22.133 [4] > A-Processing #10
15:34:22.134 [7] > B-Processing #3
15:34:22.134 [6] > B-Processing #4
15:34:22.553 [5] > B-Processing #5
15:34:22.957 [8] > B-Processing #6
15:34:22.958 [9] > B-Processing #7
15:34:23.134 [4] > A-Finished
15:34:23.134 [4] > B-Processing #8
15:34:23.135 [7] > B-Processing #9
15:34:23.135 [6] > B-Processing #10
15:34:24.135 [5] > B-Finished

(Try it on Fiddle)

您可以看到并行循环 A 最初使用 3 个线程(线程 4、6 和 7),而并行循环 B 仅使用线程 5。此时 ThreadPool 已饱和。大约 500 毫秒后,新线程 8 被注入,并被 A 循环占用。 B 循环仍然只有一个线程。又过了一秒,又注入了一个线程,线程 9。这也适用于循环A,将比分定为5-1,有利于循环A。这场战斗没有礼貌或礼貌。这是对有限资源的激烈竞争。如果您希望有多个并行循环并行运行,请确保所有循环都配置了MaxDegreeOfParallelism 选项,并且ThreadPool 可以按需创建足够的线程来容纳所有线程。


注意:以上文字描述了静态Parallel 类(.NET 5)的现有行为。通过PLINQ(AsParallel LINQ 运算符)实现的并行性在所有方面都有不同的行为。同样在未来Parallel 类可能会获得具有不同默认值的新方法。

¹ 通过ThreadPool.SetMinThreads方法配置,AFAIK默认等于Environment.ProcessorCount

【讨论】:

以上是关于.Net 中的多个 Parallel.ForEach 循环的主要内容,如果未能解决你的问题,请参考以下文章

如何将多个“CssClass”分配给asp.net中的控件

.Net 中的多个音频输出

VB.NET - 一个查询中的多个结果集?

将表单数据传递给.NET中的多个操作[重复]

同一个 .net 解决方案中的多个 edmx

将多个图像从asp.net中的文件夹保存到本地