Powershell 可以并行运行命令吗?

我有一个 Powershell 脚本来做一些批处理的一堆图像,我想做一些并行处理。Powershell 似乎有一些后台处理选项,比如 start-job、 wait-job 等等,但是我发现唯一可以进行并行处理的资源是写出脚本文本并运行它们(PowerShell 多线程)

理想情况下,我希望在.net4中使用类似于并行 foreach 的内容。

一些看起来很不起眼的东西:

foreach-parallel -threads 4 ($file in (Get-ChildItem $dir))
{
.. Do Work
}

也许我还不如直接去..。

280851 次浏览

您可以使用 背景工作在 Powershell 2中执行并行作业。

# Loop through the server list
Get-Content "ServerList.txt" | %{


# Define what each job does
$ScriptBlock = {
param($pipelinePassIn)
Test-Path "\\$pipelinePassIn\c`$\Something"
Start-Sleep 60
}


# Execute the jobs in parallel
Start-Job $ScriptBlock -ArgumentList $_
}


Get-Job


# Wait for it all to complete
While (Get-Job -State "Running")
{
Start-Sleep 10
}


# Getting the information back from the jobs
Get-Job | Receive-Job

后台作业的设置成本很高,而且不可重用 具有 PowerShell 的 一个很好的例子多线程。

(10/25/2010 site is down,but access via the Web Archive).

我在这里使用了经过改编的 Oisin 脚本,用于数据加载例程:

Http://rsdd.codeplex.com/sourcecontrol/changeset/view/a6cd657ea2be#invoke-rsddthreaded.ps1

史蒂夫 · 汤森德的答案在理论上是正确的,但在实践中却不是@likwid 指出的那样。我修改后的代码考虑到了 工作环境障碍——默认情况下没有任何东西可以跨越这个障碍!因此,自动 $_变量可以在循环中使用,但不能直接在脚本块中使用,因为它位于作业创建的单独上下文中。

要将变量从父上下文传递到子上下文,请使用 Start-Job上的 -ArgumentList参数发送变量,并使用脚本块内的 param接收变量。

cls
# Send in two root directory names, one that exists and one that does not.
# Should then get a "True" and a "False" result out the end.
"temp", "foo" | %{


$ScriptBlock = {
# accept the loop variable across the job-context barrier
param($name)
# Show the loop variable has made it through!
Write-Host "[processing '$name' inside the job]"
# Execute a command
Test-Path "\$name"
# Just wait for a bit...
Start-Sleep 5
}


# Show the loop variable here is correct
Write-Host "processing $_..."


# pass the loop variable across the job-context barrier
Start-Job $ScriptBlock -ArgumentList $_
}


# Wait for all to complete
While (Get-Job -State "Running") { Start-Sleep 2 }


# Display output from all jobs
Get-Job | Receive-Job


# Cleanup
Remove-Job *

(我通常喜欢提供 PowerShell 文档的参考作为支持证据,但是,唉,我的搜索一直没有结果。如果您碰巧知道上下文分离是在哪里记录的,请在这里发表评论让我知道!)

Http://gallery.technet.microsoft.com/scriptcenter/invoke-async-allows-you-to-83b0c9f0

我创建了一个调用-异步,它允许您同时运行多个脚本块/cmdlet/函数。这对于小型作业(子网扫描或对100台计算机的 wmi 查询)非常有用,因为创建运行空间的开销相对于启动作业的启动时间是相当大的。可以这样使用。

使用脚本块,

$sb = [scriptblock] {param($system) gwmi win32_operatingsystem -ComputerName $system | select csname,caption}


$servers = Get-Content servers.txt


$rtn = Invoke-Async -Set $server -SetParam system  -ScriptBlock $sb

只是 cmdlet/function

$servers = Get-Content servers.txt


$rtn = Invoke-Async -Set $servers -SetParam computername -Params @{count=1} -Cmdlet Test-Connection -ThreadCount 50

要完成以前的答案,您还可以使用 Wait-Job等待所有作业完成:

For ($i=1; $i -le 3; $i++) {
$ScriptBlock = {
Param (
[string] [Parameter(Mandatory=$true)] $increment
)


Write-Host $increment
}


Start-Job $ScriptBlock -ArgumentList $i
}


Get-Job | Wait-Job | Receive-Job

现在这个问题有很多答案:

  1. 工件(或 PS6/7的螺纹工件或 PS5的模组)
  2. 启动过程
  3. 工作流程(只适用于 PS5)
  4. 另一个运行空间的 Powershell API
  5. 具有多台计算机的 call-command,这些计算机都可以是 localhost (必须是 admin)
  6. ISE 中的多个会话(运行空间)选项卡,或者远程 Powershell ISE 选项卡
  7. Powershell 7有一个 foreach-object -parallel作为 # 4的替代品

在 Powershell 5.1中使用 start-threadjob:

# test-netconnection has a miserably long timeout
echo yahoo.com facebook.com |
start-threadjob { test-netconnection $input } | receive-job -wait -auto


WARNING: Name resolution of yahoo.com microsoft.com facebook.com failed

它是这样工作的,虽然没有 Powershell 7中那么漂亮和 foreach-object 并行,但是也可以。

echo yahoo.com facebook.com |
% { $_ | start-threadjob { test-netconnection $input } } |
receive-job -wait -auto | ft -a


ComputerName RemotePort RemoteAddress PingSucceeded PingReplyDetails (RTT) TcpTestS
ucceeded
------------ ---------- ------------- ------------- ---------------------- --------
facebook.com 0          31.13.71.36   True          17 ms                  False
yahoo.com    0          98.137.11.163 True          97 ms                  False

这里的工作流与 foreach 并行:

workflow work {
foreach -parallel ($i in 1..3) {
sleep 5
"$i done"
}
}


work


3 done
1 done
2 done

或者一个带有并行块的工作流:

function sleepfor($time) { sleep $time; "sleepfor $time done"}


workflow work {
parallel {
sleepfor 3
sleepfor 2
sleepfor 1
}
'hi'
}
    

work


sleepfor 1 done
sleepfor 2 done
sleepfor 3 done
hi

下面是一个带有 runspace 的 API 示例:

$a =  [PowerShell]::Create().AddScript{sleep 5;'a done'}
$b =  [PowerShell]::Create().AddScript{sleep 5;'b done'}
$c =  [PowerShell]::Create().AddScript{sleep 5;'c done'}
$r1,$r2,$r3 = ($a,$b,$c).begininvoke() # run in background
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3) # wait
($a,$b,$c).streams.error # check for errors
($a,$b,$c).dispose() # clean


a done
b done
c done

在 Powershell 7中,您可以使用 每个对象-并行

$Message = "Output:"
Get-ChildItem $dir | ForEach-Object -Parallel {
"$using:Message $_"
} -ThrottleLimit 4

如果您正在使用最新的跨平台 powershell (顺便说一下,您应该使用它) https://github.com/powershell/powershell#get-powershell,您可以添加单个 &来运行并行脚本。(使用 ;按顺序运行)

在我的例子中,我需要并行运行2个 npm 脚本: npm run hotReload & npm run dev


您还可以将 npm 设置为对其脚本使用 powershell(默认情况下,它在 Windows 上使用 cmd)。

从项目根文件夹运行: npm config set script-shell pwsh --userconfig ./.npmrc 然后使用单个 npm 脚本命令: npm run start

"start":"npm run hotReload & npm run dev"

这个问题已经得到了彻底的回答,只是想把我基于 Powershell-Jobs 创建的这个方法作为参考发布出去。

作业以脚本块列表的形式传递,它们可以被参数化。 作业的输出使用颜色编码,并以作业索引作为前缀(就像在 vs-build-process 中一样,因为这将在构建中使用) 可用于一次启动多个服务器或并行运行生成步骤。

function Start-Parallel {
param(
[ScriptBlock[]]
[Parameter(Position = 0)]
$ScriptBlock,


[Object[]]
[Alias("arguments")]
$parameters
)


$jobs = $ScriptBlock | ForEach-Object { Start-Job -ScriptBlock $_ -ArgumentList $parameters }
$colors = "Blue", "Red", "Cyan", "Green", "Magenta"
$colorCount = $colors.Length


try {
while (($jobs | Where-Object { $_.State -ieq "running" } | Measure-Object).Count -gt 0) {
$jobs | ForEach-Object { $i = 1 } {
$fgColor = $colors[($i - 1) % $colorCount]
$out = $_ | Receive-Job
$out = $out -split [System.Environment]::NewLine
$out | ForEach-Object {
Write-Host "$i> "-NoNewline -ForegroundColor $fgColor
Write-Host $_
}
                

$i++
}
}
} finally {
Write-Host "Stopping Parallel Jobs ..." -NoNewline
$jobs | Stop-Job
$jobs | Remove-Job -Force
Write-Host " done."
}
}

样本输出:

sample output

PowerShell 7.0预览版3中有一个新的内置解决方案。 面向每个对象的并行特性

所以你可以这样做:

Get-ChildItem $dir | ForEach-Object -Parallel {


.. Do Work
$_ # this will be your file


}-ThrottleLimit 4