更新 -虽然这个答案说明了过程和PowerShell运行空间的机制,以及他们如何能帮助你的多线程非连续工作负载,同胞的PowerShell爱好者股神“饼干怪兽” F已经加倍努力,并纳入这些相同的概念到一个单一的工具称为 -它会执行我在下面描述的操作,自那以后,他用可选的开关进行了扩展,用于记录日志并准备了会话状态,包括导入的模块,非常酷的东西-我强烈建议您在构建自己的闪亮解决方案之前先进行检查!Invoke-Parallel
使用并行运行空间执行:
减少不可避免的等待时间
在原始的特定情况下,调用的可执行文件具有一个/nowait
选项,该选项可防止在作业(在这种情况下为时间重新同步)自行完成时阻止调用线程。
从发行者的角度来看,这大大减少了总体执行时间,但是连接到每台机器仍然是按顺序进行的。顺序连接到数千个客户端可能要花费很长时间,具体取决于由于超时等待的累积而由于某种原因或另一种原因而无法访问的计算机的数量。
为了避免在单个或几个连续超时的情况下必须将所有后续连接排队,我们可以将连接和调用命令的工作分派到单独的PowerShell Runspace,并并行执行。
什么是运行空间?
运行空间是在其中执行Powershell代码的虚拟容器,并从PowerShell语句/命令的角度表示/保存环境。
广义上讲,1个Runspace = 1个执行线程,因此我们需要对PowerShell脚本进行“多线程”操作的是Runspace的集合,然后可以依次并行执行。
像原始问题一样,调用命令多个运行空间的工作可以分解为:
- 创建一个RunspacePool
- 将PowerShell脚本或等效的可执行代码段分配给RunspacePool
- 异步调用代码(即不必等待代码返回)
RunspacePool模板
PowerShell有一个称为类型加速器的类型加速器[RunspaceFactory]
,它将帮助我们创建运行空间组件-让我们开始使用它
1.创建一个RunspacePool并执行以下操作Open()
:
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,8)
$RunspacePool.Open()
这两个参数传递给CreateRunspacePool()
,1
而且8
是最小的,并在任何给定的时间执行的运行空间的最大数量,给我们一个有效的最大并行度的8。
2.创建一个PowerShell实例,将一些可执行代码附加到该实例,并将其分配给我们的RunspacePool:
PowerShell的实例与powershell.exe
进程(实际上是主机应用程序)不同,而是一个内部运行时对象,代表要执行的PowerShell代码。我们可以使用[powershell]
类型加速器在PowerShell中创建一个新的PowerShell实例:
$Code = {
param($Credentials,$ComputerName)
$session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument("computer1.domain.tld")
$PSinstance.RunspacePool = $RunspacePool
3.使用APM异步调用PowerShell实例:
使用.NET开发术语中称为“ 异步编程模型”的方法,我们可以将命令的调用分为一种Begin
方法,该方法给出执行代码的“绿灯”,以及一种End
收集结果的方法。由于在这种情况下,我们对任何反馈都没有真正的兴趣(无论如何我们都不会等待输出w32tm
),因此可以通过简单地调用第一个方法来做出应有的决定。
$PSinstance.BeginInvoke()
将其包装在RunspacePool中
使用以上技术,我们可以将创建新连接和调用远程命令的顺序迭代包装在并行执行流中:
$ComputerNames = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName
$Code = {
param($Credentials,$ComputerName)
$session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$creds = Get-Credential domain\user
$rsPool = [runspacefactory]::CreateRunspacePool(1,8)
$rsPool.Open()
foreach($ComputerName in $ComputerNames)
{
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument($ComputerName)
$PSinstance.RunspacePool = $rsPool
$PSinstance.BeginInvoke()
}
假设CPU具有一次执行所有8个运行空间的能力,我们应该能够看到执行时间大大减少,但是由于使用了相当“先进”的方法,因此以脚本的可读性为代价。
确定最佳并行度:
我们可以轻松地创建一个RunspacePool,以允许同时执行100个运行空间:
[runspacefactory]::CreateRunspacePool(1,100)
但是归根结底,这取决于我们本地CPU可以处理多少个执行单元。换句话说,只要您的代码正在执行,那么允许的运行空间就比逻辑处理器可将代码执行分派到的运行空间要多。
由于有了WMI,可以很容易地确定此阈值:
$NumberOfLogicalProcessor = (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
[runspacefactory]::CreateRunspacePool(1,$NumberOfLogicalProcessors)
另一方面,如果由于网络延迟等外部因素导致您正在执行的代码本身导致大量等待时间,那么您仍然可以通过运行比逻辑处理器更多的同时运行空间而受益,因此您可能需要进行测试可能的最大运行空间范围以找到收支平衡:
foreach($n in ($NumberOfLogicalProcessors..($NumberOfLogicalProcessors*3)))
{
Write-Host "$n: " -NoNewLine
(Measure-Command {
$Computers = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName -First 100
...
[runspacefactory]::CreateRunspacePool(1,$n)
...
}).TotalSeconds
}