Parallel.ForEach没有利用所有可用的线程池线程

11

为什么当我运行下面的示例时,Parallel.ForEach只使用与我的计算机核心数量相同的线程数? 我以为Parallel.ForEach会给你大约1000个线程池线程?

            int threads1;
            int threads2;

            ThreadPool.GetAvailableThreads(out threads1,out threads2);
            var list = Enumerable.Range(1, 200);
            var po = new ParallelOptions
            {
                MaxDegreeOfParallelism = 100
            };

            Parallel.ForEach(list, po, x =>
                {
                    Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(1000);
                });

我有什么地方理解不对吗?


4
为什么你将最大并行度设置为100,却得到了1000个线程?尤其是当列表中只有200项时?其他800个线程应该做什么?值得注意的是,在性能和线程数量之间存在折衷,增加线程数量可能会降低性能而不是提高性能。 - Ron Beyer
1
不,我希望使用Parallel.ForEach将它们分成两批,每批100个进行处理。我不希望出现1000多个线程。 - user183872
这将是一个并行性为2的方案(2个线程,每个线程100个工作项),我仍然看不出这里使用1000个线程的原因?按照您目前的方式,它是由100个“批次”组成,每个批次有2个项。 - Ron Beyer
Daniel Moth(TPL)在2008年PDC上做了一个很好的演示,解释了为什么这是一个不好的想法。https://channel9.msdn.com/Blogs/pdc2008/TL26 - bic
1
回答你的问题,处理器每个核心只能处理一个线程,所以即使你将它们分成100批,并且在核心之间均匀分配,你也只会得到每个核心25个线程。由于它一次只能运行1个线程,因此看起来它是每个核心一个线程。它应该在休眠时让出下一个准备在该核心上运行的线程。 - Ron Beyer
显示剩余5条评论
2个回答

16

Parallel.ForEach使用托管线程池来调度并行操作,线程数量由ThreadPool.SetMinThreadsThreadPool.SetMaxThreads设置。默认情况下,最小线程数设置为系统上的处理器数量。

为了尽量减少系统资源的使用,池线程的数量保持尽可能低。当所有池线程都忙于执行操作时,调度程序逐渐生成新线程。

MaxDegreeOfParallelism通常用于防止Parallel.For同时安排多于指定数量的任务。在长时间计算的情况下,不必使用超过核心数的线程,这时它就很有用。

如果您通过增加休眠时间Thread.Sleep(100000);修改代码,您将看到新线程的创建。

如果您在Parallel.ForEach之前调用ThreadPool.SetMinThreads(100, 100);,您将看到同时启动所有的100个操作。


好的,我使用了默认的MaxDegreeOfParallelism,然后增加了超时时间,确实逐渐增加了线程数。我的真实应用程序(不像示例)使用带有数据库I/O的async/await任务。我希望能够并行地使用超过8个线程(基于核心),因为大部分都是I/O绑定到数据库上。如何保证它会利用可用的1000多个线程中的大部分? - user183872
将MaxDegreeOfParallelism和MinThreads都设置为100。 - Andrey Nasonov
1
如果您正在使用async/await,请考虑使用Task.Delay而不是Thread.Sleep - Andrey Nasonov

3

如果线程数不超过处理器核心数,您将获得最佳性能。

每个核心一次只能处理一个线程。如果线程数超过核心数,则操作系统必须在线程之间进行切换。上下文切换是一项昂贵的操作,在多线程应用程序中应尽量避免。

如果您执行的操作是IO绑定的,则应使用Task而不是Parallel.For。这在Scott Hanselman's博客上有很好的解释。

Parallel.For线程管理的详细信息在Andrey Nasonov的答案中有详细说明,因此我不会重复它。

如果您想了解更多关于线程、TPL和异步I/O的知识,我建议您阅读CLR via C#书籍


如果每个循环中的任务(在我的情况下为每个循环条目的4个)是异步的,那么我应该使用ForEach<Async>而不是Parallel.ForEach吗? - user183872

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接