函数并行执行与合理实例数:如何确定Parallel.ForEach最优并行度?
刚好之前做过类似的并行调优,来给你捋清楚这些问题:
一、怎么找到最优的并行任务数量?
你说得太对了,直接把MaxDegreeOfParallelism设成50、100这种大数值完全不合理——线程上下文切换的开销会直接吃掉并行带来的收益,搞不好比串行处理还慢。找最优值得结合任务类型,再配合实际测试:
CPU密集型任务(比如大量计算、数据转换):
这类任务的线程基本一直占着CPU跑,几乎没有等待时间。最优值一般和你的CPU逻辑核心数相当(比如4核设4),或者核心数+1(防止某个线程偶尔等待系统资源时,空出的核心能被额外线程利用)。
其实Parallel.ForEach默认不指定MaxDegreeOfParallelism时,就是用Environment.ProcessorCount(当前机器的逻辑核心数),这个值对CPU密集型任务已经非常合适了。IO密集型任务(比如读写文件、调用远程API、数据库操作):
这类任务的线程大部分时间在等IO响应,CPU处于空闲状态。这时候可以把并行数设得高一些,比如核心数的2~4倍,甚至更高——具体得看IO等待的时长。比如调用慢响应的API时,设到16、32都可能比4更快。实测调优才是最准确的方式:
最靠谱的方法是写个简单的测试脚本,循环尝试不同的MaxDegreeOfParallelism值,记录每个值的总耗时。比如从4开始,试8、16、32、50,看耗时什么时候降到最低,再往上加耗时反而上升,那个临界点就是最优值。
举个测试的小示例:for (int degree = 4; degree <= 50; degree += 4) { var stopwatch = Stopwatch.StartNew(); Parallel.ForEach(hugeObjectList, new ParallelOptions { MaxDegreeOfParallelism = degree }, obj => DoSomeWork(obj)); stopwatch.Stop(); Console.WriteLine($"并行度{degree},耗时:{stopwatch.ElapsedMilliseconds}ms"); }
另外要注意:如果DoSomeWork里涉及共享资源需要加锁,那并行数太高会导致大量锁竞争,反而变慢,这时候最优值可能会比核心数还低。
二、Parallel.ForEach会把任务分配到不同CPU核心上吗?
必须会!.NET的TPL(任务并行库)会和操作系统的线程调度器配合,自动把任务分配到不同的CPU核心上,充分利用多核资源。虽然不能保证每个线程固定绑定在一个核心上,但整体会尽量让各个核心的负载均衡,避免单核心跑满、其他核心空闲的情况。
三、4个核心设并行度为4是否正确?
分情况来看:
- 如果是CPU密集型任务:这个设置非常合理,每个线程占满一个核心,线程切换开销极小,能最大化利用CPU资源。
- 如果是IO密集型任务:4就不够了,因为很多线程在等待IO时,核心会处于空闲状态,这时候调大并行数(比如8、16)能让更多任务在等待的间隙,其他任务利用核心,从而缩短总耗时。
内容的提问来源于stack exchange,提问作者Doc

