在一个.Net进程中,只有一个托管线程池,我们可以根据需要通过公共属性设置最小和最大线程数。
在.Net中,我们也有Parallel.ForEach
,它从后台的托管线程池中获取线程。
在Parallel.ForEach
中,我们还可以设置MaxDegreeOfParallelism
来限制线程的最大数量。
我有两个并行运行的Parallel.ForEach
。一个将MaxDegreeOfParallelism
设置为3,另一个设置为7。
我的问题是:我的两个Parallel.ForEach
循环是否在后台使用相同的线程池。如果是,Parallel.ForEach
如何限制MaxDegreeOfParallelism
的线程。多个Parallel.ForEach
循环和一个托管线程池如何一起工作?如果你能在我进入.net核心源代码之前提供一个高层次的解释或一些指针,那将非常有帮助。
2条答案
按热度按时间piztneat1#
Parallel.ForEach
循环在后台使用相同的线程池吗?是的
Parallel.ForEach
如何使用MaxDegreeOfParallelism限制线程。MaxDegreeOfParallelism获取或设置this ParallelOptions示例启用的最大并发任务数。
默认情况下,Parallel类上的方法尝试使用所有可用的处理器,是不可取消的,并且以默认的TaskScheduler(TaskScheduler.Default)为目标。
Parallel.ForEach
循环和一个托管线程池如何协同工作?它们共享相同的线程池。如下所述:
通常,您不需要修改此设置。但是,您可以选择在以下高级使用方案中显式设置此设置:
当您同时运行多个算法时,需要手动定义每个算法可以占用的系统空间大小,您可以为每个算法设置MaxDegreeOfParallelism值。
r1wp621o2#
默认情况下,
Parallel.ForEach
循环使用来自ThreadPool
的线程,ThreadPool
是一个静态类,只有一个per process。可以通过配置ParallelOptions
的TaskScheduler
属性来修改此行为。创建一个自定义TaskScheduler
作为ThreadPool
的替代品并不简单,但也不是火箭科学。可以在这里找到一个实现。如果你想了解更多关于自定义任务调度器的信息,你可以阅读Stephen Toub的this文章(code)。现在,当两个并行循环同时运行时,会发生什么情况,即两者都在
ThreadPool
线程上调度工作。如果它们都配置了特定的MaxDegreeOfParallelism
,并且两者的总和不超过ThreadPool
按需创建的最小线程数¹,则两个循环在它们的调度方面不会相互干扰。当然仍然可能相互竞争CPU资源,在这种情况下,操作系统将是仲裁者。如果至少有一个并行循环没有配置特定的
MaxDegreeOfParallelism
,则此选项的有效默认值为-1
,这意味着无限并行。这将导致ThreadPool
立即饱和。并且保持饱和直到未配置的并行循环的源可枚举完成。在此期间,两个并行循环将严重干扰彼此,并且谁将获得饱和ThreadPool
将每隔~1注入的额外线程。000毫秒是谁先要求的问题。最重要的是,饱和的ThreadPool
会对任何其他独立的回调,计时器事件,异步延续等产生负面影响,这些事件也可能在此期间处于活动状态。如果两个并行循环都配置了,并且两者的总和
MaxDegreeOfParallelism
超过了可用线程的数量,那么情况与前面类似。唯一的区别是ThreadPool
中的线程数量会逐渐增加,饱和事件可能会比并行循环的执行更早结束。下面是演示此行为的示例:
输出:
(Try it on Fiddle)
可以看到,并行循环A最初使用3个线程(线程4、6和7),而并行循环B仅利用线程5。此时
ThreadPool
饱和。大约500毫秒后,新线程8被注入,并被A循环采用。B循环仍然只有一个线程。另一秒后,又有一个线程,即线程9,注入。循环A也是如此,将比分设置为5-1,有利于循环A。在这场战斗中没有礼貌或礼貌。这是对有限资源的疯狂竞争。如果您希望有多个并行循环并行运行,请确保所有循环都配置了MaxDegreeOfParallelism
选项,并且ThreadPool
可以根据需要创建足够的线程来容纳所有这些线程。¹通过方法
ThreadPool.SetMinThreads
配置,默认情况下AFAIK等于Environment.ProcessorCount
。**注意:**以上文字描述了静态
Parallel
类(.NET 5)的现有行为。通过PLINQ(AsParallel
LINQ操作符)实现的并行性在所有方面都不具有相同的行为。此外,将来Parallel
类可能会获得具有不同默认值的新方法。**.NET 6 update:**上面的例子现在产生了一个不同的输出。分数最终只有3-2,有利于循环A:
注入的线程9由循环B而不是循环A执行。似乎
Parallel
类或ThreadPool
类或两者的行为在.NET 6中略有变化。但我不确定具体变化是什么。