适用于 PowerShell 7.0 及以上版本(部分示例兼容 5.1)
随着服务器核心数增加和运维任务量增长,单线程处理已经无法满足效率需求。PowerShell 7 引入了 ForEach-Object -Parallel,让并行处理变得像管道一样简单。对于仍在使用 PowerShell 5.1 的环境,.NET 的 Runspace API 同样提供了强大的并行能力。合理使用并行处理,可以将原本需要数小时的任务缩短到几分钟。
本文将对比不同的并行方案,并给出实用的并行处理模式。
ForEach-Object -Parallel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 $servers = @ ("SRV01" , "SRV02" , "SRV03" , "SRV04" , "SRV05" )$sw = [System.Diagnostics.Stopwatch ]::StartNew()$serial = foreach ($server in $servers ) { Invoke-Command -ComputerName $server -ScriptBlock { Get-PSDrive -PSProvider FileSystem | Select-Object Name, @ {N='FreeGB' ; E={[math ]::Round($_ .Free / 1 GB, 2 )}}, @ {N='UsedGB' ; E={[math ]::Round($_ .Used / 1 GB, 2 )}} } } $sw .Stop()Write-Host "串行执行:$ ($sw .ElapsedMilliseconds) ms" $sw .Restart()$parallel = $servers | ForEach-Object -ThrottleLimit 5 -Parallel { Invoke-Command -ComputerName $_ -ScriptBlock { Get-PSDrive -PSProvider FileSystem | Select-Object Name, @ {N='FreeGB' ; E={[math ]::Round($_ .Free / 1 GB, 2 )}}, @ {N='UsedGB' ; E={[math ]::Round($_ .Used / 1 GB, 2 )}} } } $sw .Stop()Write-Host "并行执行:$ ($sw .ElapsedMilliseconds) ms" $results = 1 ..100 | ForEach-Object -ThrottleLimit 10 -Parallel { Start-Sleep -Milliseconds 50 [PSCustomObject ]@ { Input = $_ Squared = $_ * $_ Thread = [System.Threading.Thread ]::CurrentThread.ManagedThreadId } } $uniqueThreads = $results .Thread | Sort-Object -Unique Write-Host "使用了 $ ($uniqueThreads .Count) 个线程处理 100 个任务" $results | Select-Object -First 5 | Format-Table -AutoSize
执行结果示例:
1 2 3 4 5 6 7 8 9 10 串行执行:12534 ms 并行执行:3102 ms 使用了 6 个线程处理 100 个任务Input Squared Thread ----- ------- ------ 1 1 7 2 4 8 3 9 4 4 16 7 5 25 8
变量传递与状态共享 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 $threshold = 80 $warningEmail = "admin@contoso.com" $drives = @ ( @ { Server = "SRV01" ; Drive = "C:" }, @ { Server = "SRV02" ; Drive = "D:" }, @ { Server = "SRV03" ; Drive = "C:" } ) $alerts = $drives | ForEach-Object -ThrottleLimit 3 -Parallel { $thresh = $using:threshold $email = $using:warningEmail $usage = Get-Random -Minimum 50 -Maximum 100 if ($usage -gt $thresh ) { [PSCustomObject ]@ { Server = $_ .Server Drive = $_ .Drive Usage = $usage Alert = "$ ($_ .Server) $ ($_ .Drive) 使用率 $ {usage}% 超过阈值 $ {thresh}%" NotifyTo = $email } } } if ($alerts ) { Write-Host "磁盘告警:" -ForegroundColor Red $alerts | Format-Table Server, Drive, Usage, Alert -AutoSize } else { Write-Host "所有磁盘使用率正常" -ForegroundColor Green } $counter = [System.Collections.Concurrent.ConcurrentDictionary [string , int ]]::new()1 ..50 | ForEach-Object -ThrottleLimit 10 -Parallel { $dict = $using:counter $category = @ ("Web" , "API" , "DB" , "Cache" )[(Get-Random -Minimum 0 -Maximum 4 )] $dict .AddOrUpdate($category , 1 , { param ($k , $v ) $v + 1 }) } Write-Host "`n分类统计:" -ForegroundColor Cyan$counter .GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { Write-Host " $ ($_ .Key): $ ($_ .Value)" }
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 磁盘告警: Server Drive Usage Alert - ----- - ---- - ---- - ----SRV01 C: 92 SRV01 C: 使用率 92 % 超过阈值 80 % SRV03 C: 87 SRV03 C: 使用率 87 % 超过阈值 80 % 分类统计: Web: 16 API: 14 DB: 11 Cache: 9
Runspace 池(兼容 5.1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 function Invoke-RunspaceJob { param ( [Parameter (Mandatory )] [scriptblock ]$ScriptBlock , [Parameter (Mandatory )] [object []]$InputData , [int ]$ThrottleLimit = 4 ) $pool = [runspacefactory ]::CreateRunspacePool(1 , $ThrottleLimit ) $pool .Open() $jobs = [System.Collections.Generic.List [hashtable ]]::new() foreach ($item in $InputData ) { $ps = [powershell ]::Create() $ps .RunspacePool = $pool $ps .AddScript($ScriptBlock ).AddArgument($item ) | Out-Null $jobs .Add(@ { PowerShell = $ps Handle = $ps .BeginInvoke() Input = $item }) } $results = @ () foreach ($job in $jobs ) { try { $output = $job .PowerShell.EndInvoke($job .Handle) if ($output ) { $results += $output } } catch { Write-Host "任务失败:$ ($job .Input) - $ ($_ .Exception.Message)" -ForegroundColor Red } finally { $job .PowerShell.Dispose() } } $pool .Close() $pool .Dispose() return $results } $computers = @ ( "SRV01" , "SRV02" , "SRV03" , "SRV04" , "SRV05" , "DB01" , "DB02" , "WEB01" , "WEB02" , "APP01" ) $pingResults = Invoke-RunspaceJob -InputData $computers -ThrottleLimit 5 -ScriptBlock { param ([string ]$Computer ) $sw = [System.Diagnostics.Stopwatch ]::StartNew() $ping = Test-Connection -ComputerName $Computer -Count 1 -Quiet -ErrorAction SilentlyContinue $sw .Stop() [PSCustomObject ]@ { Computer = $Computer Online = $ping LatencyMs = $sw .ElapsedMilliseconds } } $pingResults | Sort-Object Computer | Format-Table -AutoSize $online = ($pingResults | Where-Object { $_ .Online }).CountWrite-Host "在线:$online / $ ($computers .Count)" -ForegroundColor Green
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 Computer Online LatencyMs -------- ------ --------- APP01 True 2 DB01 True 3 DB02 True 2 SRV01 True 1 SRV02 True 2 SRV03 True 1 SRV04 True 2 SRV05 True 2 WEB01 True 3 WEB02 True 2 在线:10 / 10
并行文件处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 function Invoke-ParallelFileProcess { param ( [Parameter (Mandatory )] [string ]$Path , [Parameter (Mandatory )] [scriptblock ]$ProcessBlock , [string ]$Filter = "*.*" , [int ]$ThrottleLimit = 4 ) $files = Get-ChildItem $Path -Filter $Filter -File Write-Host "找到 $ ($files .Count) 个文件,使用 $ThrottleLimit 并发处理" -ForegroundColor Cyan $sw = [System.Diagnostics.Stopwatch ]::StartNew() $results = $files | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel { $file = $_ $block = $using:ProcessBlock try { & $block $file } catch { [PSCustomObject ]@ { File = $file .Name Error = $_ .Exception.Message Status = "Failed" } } } $sw .Stop() $success = ($results | Where-Object { $_ .Status -ne "Failed" }).Count $failed = ($results | Where-Object { $_ .Status -eq "Failed" }).Count Write-Host "`n处理完成:成功 $success ,失败 $failed " -ForegroundColor $ (if ($failed ) { "Yellow" } else { "Green" }) Write-Host "耗时:$ ($sw .ElapsedMilliseconds) ms" return $results } $hashResults = Invoke-ParallelFileProcess -Path "C:\Projects\MyApp\bin" -Filter "*.dll" -ThrottleLimit 8 -ProcessBlock { param ($file ) $hash = (Get-FileHash $file .FullName -Algorithm SHA256).Hash [PSCustomObject ]@ { File = $file .Name SizeKB = [math ]::Round($file .Length / 1 KB, 2 ) SHA256 = $hash .Substring(0 , 16 ) + "..." Status = "OK" } } $hashResults | Format-Table -AutoSize
执行结果示例:
1 2 3 4 5 6 7 8 9 10 找到 24 个文件,使用 8 并发处理 处理完成:成功 24,失败 0 耗时:1234 ms File SizeKB SHA256 Status ---- ------ ------ ------ Microsoft.AI.dll 456.78 a1b2c3d4e5f6... OK Newtonsoft.Json 1234.56 f1e2d3c4b5a6... OK System.Text.Json 234.56 1a2b3c4d5e6f... OK MyApp.Core.dll 567.89 b2c3d4e5f6a1... OK
注意事项
并发数控制 :ThrottleLimit 不是越大越好,CPU 密集型任务设为 CPU 核心数,I/O 密集型可设为 2-4 倍
线程安全 :并行代码中不要直接修改外部变量,使用 $using: 传递只读数据,或使用 ConcurrentDictionary 共享可变状态
错误处理 :每个并行任务应有独立的 try-catch,避免一个任务失败导致整个并行操作中断
内存消耗 :每个 runspace 有独立的内存空间,大量并发会消耗更多内存
调试困难 :并行代码的调试比串行代码复杂得多,建议先用串行模式验证逻辑正确性
PowerShell 7 优势 :ForEach-Object -Parallel 是 PowerShell 7 独有功能,5.1 只能使用 Runspace 方案