Powershell 可以并行运行命令吗?

问题描述 投票:0回答:9

我有一个 powershell 脚本来对一堆图像进行一些批处理,我想做一些并行处理。 Powershell 似乎有一些后台处理选项,例如启动作业、等待作业等,但我发现用于并行工作的唯一好的资源是编写脚本文本并运行这些选项(PowerShell 多线程

理想情况下,我想要类似于 .net 4 中并行 foreach 的东西。

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

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

也许我最好还是直接转向 c#...

multithreading powershell parallel-processing
9个回答
117
投票

您可以使用后台作业在 Powershell 2 中执行并行作业。查看 Start-Job 和其他作业 cmdlet。

# 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

106
投票

Steve Townsend 的答案在理论上是正确的,但在实践中并不像 @likwid 指出的那样。我修改后的代码考虑了工作上下文障碍——默认情况下没有任何东西可以跨越该障碍!因此,自动

$_
变量可以在循环中使用,但不能直接在脚本块中使用,因为它位于由作业创建的单独上下文中。

要将变量从父上下文传递到子上下文,请使用

-ArgumentList
上的
Start-Job
参数来发送它,并在脚本块内使用
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 文档的参考作为支持证据,但是,唉,我的搜索毫无结果。如果您碰巧知道上下文分离的记录在哪里,请在此处发表评论让我知道!)


29
投票

这些天有很多答案:

  1. 作业(或 PS 6/7 中的线程作业或 PS 5 的模块)
  2. 启动进程
  3. 工作流程(仅限 PS 5)
  4. powershell api 与另一个运行空间
  5. 多台计算机调用命令,都可以是本地主机(必须是管理员)
  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 -parallel,但它可以。

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 -parallel:

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

这是一个带有运行空间示例的 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

13
投票

在 Powershell 7 中,您可以使用 ForEach-Object -Parallel

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

8
投票

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/函数

$servers = Get-Content servers.txt 

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

7
投票

要完成之前的答案,您还可以使用

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

6
投票

如果您使用最新的跨平台 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"

3
投票

PowerShell 7.0 Preview 3 中有一个新的内置解决方案。 PowerShell ForEach-Object 并行功能

所以你可以这样做:

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

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

}-ThrottleLimit 4

2
投票

这个问题已经得到了彻底的回答。只是想发布我基于 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."
    }
}

示例输出:

© www.soinside.com 2019 - 2024. All rights reserved.