为何手动并行任务代码无法达到100% CPU利用率?
问题根源分析与解决方案
你遇到的核心问题其实有两个,正好解释了为什么手动“并行”代码的CPU利用率和单线程一致,还不如PLINQ高效:
1. 你的任务根本没并行运行!
看一下你的SumParallel方法里的循环:
for(int i = 1; i < 5; i++) { int skip = ((i - 1) * count) / 4; int take = count / 4; Interlocked.Add(ref total, await GetSum(intList, skip, take)); }
这里的await GetSum(...)会让当前线程等待这个任务完全结束后,才会进入下一次循环启动下一个任务。也就是说,四个任务是串行依次执行的,同一时间只有一个线程在工作。你的i3是4逻辑核心,单个核心满负载就是25%的总CPU利用率,这完全符合你看到的现象。
2. 延迟加载的Enumerable导致大量重复劳动
就算你修复了并行问题,Enumerable.Range(1, 1000_000_000)是延迟加载的序列——每次调用Skip(skip).Take(take)时,都会从头开始生成数字,跳过前skip个再取take个。比如第二个任务要先生成前2.5亿个数字然后扔掉,第三个任务要生成前5亿个扔掉,四个任务都在做重复的遍历工作,这会极大浪费CPU资源,效率极低。
而PLINQ的AsParallel()会智能处理序列分区:它能识别Enumerable.Range是连续整数序列,直接给每个线程分配一段起止范围,不需要重复生成和跳过元素,所以能把CPU拉满到100%。
修正后的手动并行代码
下面是修复了这两个问题的版本,能让CPU利用率接近100%,效率甚至可能超过PLINQ:
private static async Task<int> SumParallelFixed() { int totalCount = 1000_000_000; var tasks = new List<Task<int>>(); // 先启动所有4个任务,不等待,实现真正并行 for (int i = 1; i <= 4; i++) { int start = ((i - 1) * totalCount) / 4 + 1; int end = i * totalCount / 4; tasks.Add(CalculateSumInRange(start, end)); } // 等待所有任务完成,再汇总结果 var results = await Task.WhenAll(tasks); return results.Sum(); } private static async Task<int> CalculateSumInRange(int start, int end) { return await Task.Run(() => { int temp = 0; // 直接遍历指定范围,无重复生成/跳过操作 for (int n = start; n <= end; n++) { temp += n % 2 == 0 ? -n : n; } return temp; }); }
关键优化点:
- 用
Task.WhenAll替代循环内的逐个await,让四个任务同时启动并行执行 - 直接计算每个任务的起止范围,避免使用
Skip/Take带来的重复遍历 - 去掉了不必要的
Interlocked.Add,因为最终结果是在主线程汇总,不存在线程安全问题
内容的提问来源于stack exchange,提问作者teenup
相关产品推荐
相关产品推荐

