C#怎么使用Parallel类_C#如何充分利用多核CPU【教程】

4次阅读

Parallel.ForEach 并非总能提速,轻量操作反因调度开销变慢;适用场景为单次迭代≥几毫秒且无强依赖,需避免共享变量、控制并发度、慎用异常与阻塞调用。

Parallel.ForEach 会自动分配线程,但不等于自动变快

多数人以为只要换成 parallel.foreach 就能提速,结果反而更慢——尤其当循环体里是轻量操作(比如简单数学计算或字符串拼接)时,线程调度和同步开销可能远超收益。

真正适合并行的场景是:单次迭代耗时 ≥ 几毫秒、且彼此无强依赖。例如读写文件、调用外部 API、图像像素处理。

  • Stopwatch 实测单次 body 执行时间,低于 1ms 就别硬上 Parallel.ForEach
  • 避免在循环体内访问共享变量;真要写,用 Interlocked.IncrementConcurrentBag<T>,别直接 ++ 普通 int
  • 默认线程数由 ThreadPool 管理,但 CPU 密集型任务建议限制并发度:ParallelOptions.MaxDegreeOfParallelism = Environment.ProcessorCount

Parallel.For 的索引安全边界容易被忽略

Parallel.For 看似和普通 for 一样用,但它的迭代顺序不保证,而且多个线程可能同时进入同一段逻辑——如果你在循环里用了 list[i] = …… 这种写法,而 list 不是线程安全容器,就会出现覆盖或 IndexOutOfRangeException

  • 只对数组 / 列表做只读访问?没问题。写入?必须确认目标集合支持并发写,或改用 Parallel.ForEach + ConcurrentDictionary 等结构
  • 别在 Parallel.For 里调用 Thread.Sleep 或阻塞 I/O,这会让线程池饥饿,拖慢整个程序
  • 异常处理要小心:AggregateException 是默认包装器,ex.InnerExceptions 才是你真正要 inspect 的错误列表

Parallel.Invoke 不适合串行逻辑拆分

看到“多个任务”,第一反应用 Parallel.Invoke 把几个方法包进去?这只有在它们完全独立、执行时间接近、且都属于 CPU 密集型时才合理。否则极易造成负载不均:一个任务跑 500ms,另两个 20ms,最后等那一个拖完才返回。

  • 如果任务耗时不均,优先考虑 Task.Run + await Task.WhenAll,它不抢线程池资源,也更容易 await 和 cancel
  • Parallel.Invoke 内部没有超时机制,出问题只能靠外部 CancellationToken 中断,但中断后状态难清理
  • 参数是 Action[],传 lambda 时注意闭包捕获的变量是否线程安全;尤其别在循环里写 Parallel.Invoke(() => Do(x)),x 很可能被所有任务共享

别忘了 Parallel LINQ(PLINQ)这个更自然的入口

如果你已经在用 LINQ 查询集合,加个 AsParallel() 比硬套 Parallel.ForEach 更直观,而且 PLINQ 自动处理分区、合并、异常聚合。

  • 不是所有 LINQ 操作都支持并行加速:OrderByGroupBy 效果明显;FirstAny 可能提前退出,反而比串行慢
  • WithDegreeOfParallelism(n) 控制并发数,避免默认值在高核机器上开太多线程
  • PLINQ 默认保持顺序(AsOrdered()),如不需要,显式调用 AsUnordered() 能提升 10–20% 吞吐

多核不是银弹,Parallel 类只是工具箱里的一把扳手——拧得越用力,越要看清螺纹方向。实际项目里,先 profile 瓶颈点,再决定是换算法、加缓存,还是真需要并行。盲目套 Parallel,常常是给 GC 和线程调度添乱。

星耀云
版权声明:本站原创文章,由 星耀云 2026-03-19发表,共计1535字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources