AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / server / 问题 / 626711
Accepted
Mathias R. Jessen
Mathias R. Jessen
Asked: 2014-09-07 05:56:22 +0800 CST2014-09-07 05:56:22 +0800 CST 2014-09-07 05:56:22 +0800 CST

如何在不使用作业的情况下并行运行我的 PowerShell 脚本?

  • 772

如果我有一个脚本需要在多台计算机上运行,​​或者使用多个不同的参数,我怎样才能并行执行它,而不必产生产生新PSJobStart-Job的开销?

例如,我想重新同步所有域成员的时间,如下所示:

$computers = Get-ADComputer -filter * |Select-Object -ExpandProperty dnsHostName
$creds = Get-Credential domain\user
foreach($computer in $computers)
{
    $session = New-PSSession -ComputerName $computer -Credential $creds
    Invoke-Command -Session $session -ScriptBlock { w32tm /resync /nowait /rediscover }
}

但我不想等待每个 PSSession 连接并调用命令。没有乔布斯,这怎么能并行完成?

performance
  • 4 4 个回答
  • 57907 Views

4 个回答

  • Voted
  1. Best Answer
    Mathias R. Jessen
    2014-09-07T05:56:22+08:002014-09-07T05:56:22+08:00

    更新 - 虽然这个答案解释了 PowerShell 运行空间的过程和机制,以及它们如何帮助您处理多线程非顺序工作负载,但 PowerShell 爱好者Warren 'Cookie Monster' F已经加倍努力,并将这些相同的概念整合到一个工具中称为 - 它执行我在下面描述的操作,并且他已经使用可选开关进行了扩展,用于记录和准备会话状态,包括导入的模块,非常酷的东西 - 我强烈建议您在构建自己的闪亮解决方案之前检查它!Invoke-Parallel


    使用并行运行空间执行:

    减少不可避免的等待时间

    在最初的特定情况下,被调用的可执行文件有一个/nowait选项可以防止在作业(在这种情况下,时间重新同步)自行完成时阻塞调用线程。

    从发行者的角度来看,这大大减少了整体执行时间,但连接到每台机器仍然是按顺序完成的。由于超时等待的累积,按顺序连接到数千个客户端可能需要很长时间,具体取决于由于某种原因或其他原因而无法访问的机器数量。

    为了避免在单个或几个连续超时的情况下将所有后续连接排队,我们可以将连接和调用命令的作业分派到单独的 PowerShell 运行空间,并行执行。

    什么是运行空间?

    运行空间是您的powershell代码在其中执行的虚拟容器,并从 PowerShell 语句/命令的角度表示/保存环境。

    从广义上讲,1 个运行空间 = 1 个执行线程,因此我们对 PowerShell 脚本“多线程”所需的只是运行空间的集合,这些运行空间随后可以并行执行。

    与最初的问题一样,调用多个运行空间的命令的工作可以分解为:

    1. 创建运行空间池
    2. 将 PowerShell 脚本或等效的可执行代码分配给 RunspacePool
    3. 异步调用代码(即不必等待代码返回)

    运行空间池模板

    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
    }
    
    • 58
  2. Nate Stone
    2017-02-10T08:22:37+08:002017-02-10T08:22:37+08:00

    除了这个讨论之外,还缺少一个收集器来存储从运行空间创建的数据,以及一个用于检查运行空间状态的变量,即它是否已完成。

    #Add an collector object that will store the data
    $Object = New-Object 'System.Management.Automation.PSDataCollection[psobject]'
    
    #Create a variable to check the status
    $Handle = $PSinstance.BeginInvoke($Object,$Object)
    
    #So if you want to check the status simply type:
    $Handle
    
    #If you want to see the data collected, type:
    $Object
    
    • 5
  3. Rosco
    2017-12-07T15:21:10+08:002017-12-07T15:21:10+08:00

    查看PoshRSJob。它提供与本机 *-Job 函数相同/相似的功能,但使用的运行空间往往比标准 Powershell 作业更快且响应更快。

    • 3
  4. phbits
    2019-04-26T15:57:00+08:002019-04-26T15:57:00+08:00

    @mathias-r-jessen 有一个很好的答案,尽管我想补充一些细节。

    最大线程数

    理论上线程应该受到系统处理器数量的限制。但是,在测试AsyncTcpScan时,我通过为MaxThreads. 因此,为什么该模块具有-MaxThreads输入参数。请记住,分配太多线程会影响性能。

    返回数据

    从那里取回数据ScriptBlock很棘手。我已经更新了 OP 代码并将其集成到用于AsyncTcpScan的代码中。

    警告:我无法测试以下代码。根据我使用 Active Directory cmdlet 的经验,我对 OP 脚本进行了一些更改。

    # Script to run in each thread.
    [System.Management.Automation.ScriptBlock]$ScriptBlock = {
    
        $result = New-Object PSObject -Property @{ 'Computer' = $args[0];
                                                   'Success'  = $false; }
    
        try {
                $session = New-PSSession -ComputerName $args[0] -Credential $args[1]
                Invoke-Command -Session $session -ScriptBlock { w32tm /resync /nowait /rediscover }
                Disconnect-PSSession -Session $session
                $result.Success = $true
        } catch {
    
        }
    
        return $result
    
    } # End Scriptblock
    
    function Invoke-AsyncJob
    {
        [CmdletBinding()]
        param(
            [parameter(Mandatory=$true)]
            [System.Management.Automation.PSCredential]
            # Credential object to login to remote systems
            $Credentials
        )
    
        Import-Module ActiveDirectory
    
        $Results = @()
    
        $AllJobs = New-Object System.Collections.ArrayList
    
        $AllDomainComputers = Get-ADComputer -Filter * -Properties dnsHostName
    
        $HostRunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(2,10,$Host)
    
        $HostRunspacePool.Open()
    
        foreach($DomainComputer in $AllDomainComputers)
        {
            $asyncJob = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock).AddParameters($($($DomainComputer.dnsName),$Credentials))
    
            $asyncJob.RunspacePool = $HostRunspacePool
    
            $asyncJobObj = @{ JobHandle   = $asyncJob;
                              AsyncHandle = $asyncJob.BeginInvoke()    }
    
            $AllJobs.Add($asyncJobObj) | Out-Null
        }
    
        $ProcessingJobs = $true
    
        Do {
    
            $CompletedJobs = $AllJobs | Where-Object { $_.AsyncHandle.IsCompleted }
    
            if($null -ne $CompletedJobs)
            {
                foreach($job in $CompletedJobs)
                {
                    $result = $job.JobHandle.EndInvoke($job.AsyncHandle)
    
                    if($null -ne $result)
                    {
                        $Results += $result
                    }
    
                    $job.JobHandle.Dispose()
    
                    $AllJobs.Remove($job)
                } 
    
            } else {
    
                if($AllJobs.Count -eq 0)
                {
                    $ProcessingJobs = $false
    
                } else {
    
                    Start-Sleep -Milliseconds 500
                }
            }
    
        } While ($ProcessingJobs)
    
        $HostRunspacePool.Close()
        $HostRunspacePool.Dispose()
    
        return $Results
    
    } # End function Invoke-AsyncJob
    
    • 3

相关问题

  • 基于 Microsoft 的服务器(IIS、MSSQL 等)上的病毒扫描应排除哪些内容?

  • jvm性能调优技巧/资源?

  • 加快 MSSQL 快照复制到 SQLExpress 副本的速度

  • 聚集索引与非聚集索引?

  • 使用大量 javascript 的页面上的鱿鱼速度很慢

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    新安装后 postgres 的默认超级用户用户名/密码是什么?

    • 5 个回答
  • Marko Smith

    SFTP 使用什么端口?

    • 6 个回答
  • Marko Smith

    命令行列出 Windows Active Directory 组中的用户?

    • 9 个回答
  • Marko Smith

    什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同?

    • 3 个回答
  • Marko Smith

    如何确定bash变量是否为空?

    • 15 个回答
  • Martin Hope
    Tom Feiner 如何按大小对 du -h 输出进行排序 2009-02-26 05:42:42 +0800 CST
  • Martin Hope
    Noah Goodrich 什么是 Pem 文件,它与其他 OpenSSL 生成的密钥文件格式有何不同? 2009-05-19 18:24:42 +0800 CST
  • Martin Hope
    Brent 如何确定bash变量是否为空? 2009-05-13 09:54:48 +0800 CST
  • Martin Hope
    cletus 您如何找到在 Windows 中打开文件的进程? 2009-05-01 16:47:16 +0800 CST

热门标签

linux nginx windows networking ubuntu domain-name-system amazon-web-services active-directory apache-2.4 ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve