PLINQ 是 LINQ 的并行扩展,不是替代品
PLINQ 不是另一个 LINQ——它只是在原有
Enumerable查询链上加个
AsParallel(),就自动启用多线程并行处理。本质仍是 LINQ to Objects,语法、返回类型、延迟执行特性全一样。区别只在执行方式:LINQ 单线程顺序跑,PLINQ 尝试把数据切片、分发到多个 CPU 核心上并发跑。
什么时候该用 AsParallel()
?看三件事
别凭直觉加并行。实际是否提速,取决于这三点是否同时满足:
数据量够大(通常 ≥ 10⁵ 项,且每个元素处理耗时 ≥ 几微秒) 操作是计算密集型(如Where中做素数判断、
Select中做浮点运算、
Aggregate做累加) 数据源是内存集合(
IEnumerable<t></t>,比如
List<t></t>或
Array),**不涉及 IO(数据库/文件/网络)**
反例:对 1000 个字符串调用
.ToLower()+
AsParallel(),大概率更慢——线程调度开销压倒收益;对
DbContext.Set<t>().AsEnumerable()</t>后接
AsParallel(),看似并行,实则先把全部数据拉进内存再处理,还可能引发 EF 上下文跨线程异常。
PLINQ 容易踩的坑
加了
AsParallel()不等于万事大吉。常见翻车点:
OrderBy、
Zip、
Take等依赖顺序的操作,在 PLINQ 中默认禁用或需显式调用
AsOrdered(),否则结果顺序不确定,且
AsOrdered()会显著拖慢性能 共享可变状态(如在
Where中修改外部
int counter)会导致竞态,必须改用线程安全方式(如
Interlocked.Increment或
ConcurrentBag) 异常处理不同:PLINQ 把多个线程的异常打包进
AggregateException,直接
catch(Exception)会漏掉真正原因 某些运算符(如
First()、
Single())在 PLINQ 中仍保持“找到即停”,但底层仍可能已启动多路扫描,实际并不省力
一个真实对比示例
以下代码测的是「从 1 到 500 万中找出所有素数」:
var data = Enumerable.Range(1, 5_000_000).ToArray();
// LINQ 版(单线程)
var sw = Stopwatch.StartNew();
var primes1 = data.Where(IsPrime).ToList();
sw.Stop();
Console.WriteLine($"LINQ: {sw.ElapsedMilliseconds}ms");
// PLINQ 版(多线程)
sw.Restart();
var primes2 = data.AsParallel().Where(IsPrime).ToList();
sw.Stop();
Console.WriteLine($"PLINQ: {sw.ElapsedMilliseconds}ms");
在 8 核机器上,PLINQ 通常快 3–5 倍;但如果把范围改成
Range(1, 10_000),PLINQ 反而慢 20%~30%,就因为并行调度成本盖过了计算收益。
真正关键的判断点从来不是“要不要用 PLINQ”,而是“这个查询是不是瓶颈,且瓶颈是否能被并行摊薄”。先用
Stopwatch或性能分析器确认,再决定加不加
AsParallel()——盲目并行,比不优化还危险。
