You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何手动并行任务代码无法达到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 20:27:43