2012-01-08 20 views
14

Tengo el siguiente script de PowerShellRun N trabajos paralelos en powershell

$list = invoke-sqlcmd 'exec getOneMillionRows' -Server... 
$list | % { 
    GetData $_ > $_.txt 
    ZipTheFile $_.txt $_.txt.zip 
    ... 
} 

Cómo ejecutar el bloque de script ({ GetDatta $_ > $_.txt ....}) en paralelo con máximo número limitado de trabajo, por ejemplo, a lo sumo se pueden generar 8 archivos a la vez?

Respuesta

13

El cmdlet Start-Job le permite ejecutar código en segundo plano. Para hacer lo que pedirías, algo como el siguiente código debería funcionar.

foreach ($server in $servers) { 
    $running = @(Get-Job | Where-Object { $_.State -eq 'Running' }) 
    if ($running.Count -le 8) { 
     Start-Job { 
      Add-PSSnapin SQL 
      $list = invoke-sqlcmd 'exec getOneMillionRows' -Server... 
      ... 
     } 
    } else { 
     $running | Wait-Job 
    } 
    Get-Job | Receive-Job 
} 

Espero que esto ayude.

+1

Para acelerar el cue en 8 y seguir presionando otro trabajo en la pila mientras termina otro, creo que necesitará '$ running | Wait-Job -Any'. –

+3

'Wait-Job -Any':" Muestra el símbolo del sistema (y devuelve el objeto de trabajo) cuando finaliza un trabajo. De forma predeterminada, Wait-Job espera hasta que se completen todos los trabajos especificados antes de mostrar el mensaje ". –

+0

No estoy seguro de qué '($ server in $ server)' hace aquí. Pero tengo el ideal. – ca9163d9

9

Debería ser realmente fácil con el cmdlet Split-Pipeline del módulo SplitPipeline. El código será tan simple como esto:

Import-Module SplitPipeline 
$list = invoke-sqlcmd 'exec getOneMillionRows' -Server... 
$list | Split-Pipeline -Count 8 {process{ 
    GetData $_ > $_.txt 
    ZipTheFile $_.txt $_.txt.zip 
    ... 
}} 
+0

Me gusta mucho este módulo. Pero la única variable externa disponible dentro del bloque de canalización es $ _; ¿cómo se pasan otras variables en el bloque de tuberías? '$ a =" foo "; $ list | Split-Pipeline {# $ a no está definido aquí} ' –

+1

Las variables y funciones usadas del espacio de ejecución actual deben importarse explícitamente en tuberías paralelas utilizando los parámetros' -Variable' y '-Function'. Eventualmente, con suerte pronto, mencionaré esto en la ayuda de cmdlet o daré un ejemplo. –

+0

Gracias Romano. Me di cuenta de esto después de hacer la pregunta, y publiqué la sugerencia en GitHub sobre mencionar esto en la documentación. Esta es una herramienta muy útil, y aceleró tremendamente la tarea que estaba ejecutando. Gracias. –

5

tareas en segundo plano es la respuesta. También puede estrangular los trabajos en la cola de ejecución utilizando [System.Collection.Queue]. Hay una publicación del blog del equipo de PowerShell sobre este tema: http://blogs.msdn.com/b/powershell/archive/2011/04/04/scaling-and-queuing-powershell-background-jobs.aspx

El uso del método de colas es probablemente la mejor respuesta para la estrangulación de trabajos en segundo plano.

+0

El ejemplo en el enlace parece ejecutar solo tres trabajos. – ca9163d9

+0

Sí, ese es un ejemplo. Puede modificarlo según sus necesidades. – ravikanth

+0

Realmente ejecutará todas las tareas. Comparando con el método @ start-automaating, no necesita un bucle de sondeo. – ca9163d9

10

La misma idea que el usuario "Puesta en Automatización" publicado, pero corregido el error de olvidarse de comenzar los trabajos que se llevan a cabo volver al golpear la cláusula else en su ejemplo:

$servers = @('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n') 

foreach ($server in $servers) { 
    $running = @(Get-Job | Where-Object { $_.State -eq 'Running' }) 
    if ($running.Count -ge 4) { 
     $running | Wait-Job -Any | Out-Null 
    } 

    Write-Host "Starting job for $server" 
    Start-Job { 
     # do something with $using:server. Just sleeping for this example. 
     Start-Sleep 5 
     return "result from $using:server" 
    } | Out-Null 
} 

# Wait for all jobs to complete and results ready to be received 
Wait-Job * | Out-Null 

# Process the results 
foreach($job in Get-Job) 
{ 
    $result = Receive-Job $job 
    Write-Host $result 
} 

Remove-Job -State Completed 
+0

Gracias por esto, está funcionando. Primero probé la solución MS oficial "bug" de su blog que bloqueó mi PowerShell, incluso para 3 trabajos. Entonces, no use la solución de este sitio: https://blogs.msdn.microsoft.com/powershell/2011/04/04/scaling-and-queuing-powershell-background-jobs/ – Stritof

+0

esta es la respuesta ... :) el otro tiene un error de lógica –

+0

'Get-Job | Receive-job -AutoRemoveJob -Wait' para esperar y eliminar automáticamente todos los trabajos (en su ejemplo, no borra los trabajos con errores). – riezebosch

1

hilo viejo, pero creo esto podría ayudar:

$List = C:\List.txt 
$Jobs = 8 

Foreach ($PC in Get-Content $List) 
{ 
Do 
    { 
    $Job = (Get-Job -State Running | measure).count 
    } Until ($Job -le $Jobs) 

Start-Job -Name $PC -ScriptBlock { "Your command here $Using:PC" } 
Get-Job -State Completed | Remove-Job 
} 

Wait-Job -State Running 
Get-Job -State Completed | Remove-Job 
Get-Job 

el bucle "hacer" una pausa en el "foreach" cuando la cantidad de trabajo "en marcha" excede la cantidad de puestos de trabajo "$" que se pueden ejecutar. de esperar a que las restantes para completar y mostrar trabajos fallidos ...

1

yo uso y improove una función multi-hilo, que se puede utilizar como:

$Script = { 
    param($Computername) 
    get-process -Computername $Computername 
} 

@('Srv1','Srv2') | Run-Parallel -ScriptBlock $Script 

incluir este código en el script

function Run-Parallel { 
    <# 
     .Synopsis 
      This is a quick and open-ended script multi-threader searcher 
      http://www.get-blog.com/?p=189#comment-28834 
      Improove by Alban LOPEZ 2016 

     .Description 
      This script will allow any general, external script to be multithreaded by providing a single 
      argument to that script and opening it in a seperate thread. It works as a filter in the 
      pipeline, or as a standalone script. It will read the argument either from the pipeline 
      or from a filename provided. It will send the results of the child script down the pipeline, 
      so it is best to use a script that returns some sort of object. 

     .PARAMETER ScriptBlock 
      This is where you provide the PowerShell ScriptBlock that you want to multithread. 

     .PARAMETER ItemObj 
      The ItemObj represents the arguments that are provided to the child script. This is an open ended 
      argument and can take a single object from the pipeline, an array, a collection, or a file name. The 
      multithreading script does it's best to find out which you have provided and handle it as such. 
      If you would like to provide a file, then the file is read with one object on each line and will 
      be provided as is to the script you are running as a string. If this is not desired, then use an array. 

     .PARAMETER InputParam 
      This allows you to specify the parameter for which your input objects are to be evaluated. As an example, 
      if you were to provide a computer name to the Get-Process cmdlet as just an argument, it would attempt to 
      find all processes where the name was the provided computername and fail. You need to specify that the 
      parameter that you are providing is the "ComputerName". 

     .PARAMETER AddParam 
      This allows you to specify additional parameters to the running command. For instance, if you are trying 
      to find the status of the "BITS" service on all servers in your list, you will need to specify the "Name" 
      parameter. This command takes a hash pair formatted as follows: 

      @{"key" = "Value"} 
      @{"key1" = "Value"; "key2" = 321; "key3" = 1..9} 

     .PARAMETER AddSwitch 
      This allows you to add additional switches to the command you are running. For instance, you may want 
      to include "RequiredServices" to the "Get-Service" cmdlet. This parameter will take a single string, or 
      an aray of strings as follows: 

      "RequiredServices" 
      @("RequiredServices", "DependentServices") 

     .PARAMETER MaxThreads 
      This is the maximum number of threads to run at any given time. If ressources are too congested try lowering 
      this number. The default value is 20. 

     .PARAMETER SleepTimer_ms 
      This is the time between cycles of the child process detection cycle. The default value is 200ms. If CPU 
      utilization is high then you can consider increasing this delay. If the child script takes a long time to 
      run, then you might increase this value to around 1000 (or 1 second in the detection cycle). 

     .PARAMETER TimeOutGlobal 
      this is the TimeOut in second for listen the last thread, after this timeOut All thread are closed, only each other are returned 

     .PARAMETER TimeOutThread 
      this is the TimeOut in second for each thread, the thread are aborted at this time 

     .PARAMETER PSModules 
      List of PSModule name to include for use in ScriptBlock 

     .PARAMETER PSSapins 
      List of PSSapin name to include for use in ScriptBlock 

     .EXAMPLE 
      1..20 | Run-Parallel -ScriptBlock {param($i) Start-Sleep $i; "> $i sec <"} -TimeOutGlobal 15 -TimeOutThread 5 
     .EXAMPLE 
      Both of these will execute the scriptBlock and provide each of the server names in AllServers.txt 
      while providing the results to GridView. The results will be the output of the child script. 

      gc AllServers.txt | Run-Parallel $ScriptBlock_GetTSUsers -MaxThreads $findOut_AD.ActiveDirectory.Servers.count -PSModules 'PSTerminalServices' | out-gridview 
    #> 
    Param(
     [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 
      $ItemObj, 
     [ScriptBlock]$ScriptBlock = $null, 
     $InputParam = $Null, 
     [HashTable] $AddParam = @{}, 
     [Array] $AddSwitch = @(), 
     $MaxThreads = 20, 
     $SleepTimer_ms = 100, 
     $TimeOutGlobal = 300, 
     $TimeOutThread = 100, 
     [string[]]$PSSapins = $null, 
     [string[]]$PSModules = $null, 
     $Modedebug = $true 
    ) 
    Begin{ 
     $ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault() 
     ForEach ($Snapin in $PSSapins){ 
      [void]$ISS.ImportPSSnapIn($Snapin, [ref]$null) 
     } 
     ForEach ($Module in $PSModules){ 
      [void]$ISS.ImportPSModule($Module) 
     } 
     $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host) 
     $RunspacePool.CleanupInterval=1000 
     $RunspacePool.Open() 

     $Jobs = @() 
    } 
    Process{ 
     #ForEach ($Object in $ItemObj){ 
      if ($ItemObj){ 
       Write-Host $ItemObj -ForegroundColor Yellow 
       $PowershellThread = [powershell]::Create().AddScript($ScriptBlock) 

       If ($InputParam -ne $Null){ 
        $PowershellThread.AddParameter($InputParam, $ItemObj.ToString()) | out-null 
       }Else{ 
        $PowershellThread.AddArgument($ItemObj.ToString()) | out-null 
       } 
       ForEach($Key in $AddParam.Keys){ 
        $PowershellThread.AddParameter($Key, $AddParam.$key) | out-null 
       } 
       ForEach($Switch in $AddSwitch){ 
        $PowershellThread.AddParameter($Switch) | out-null 
       } 
       $PowershellThread.RunspacePool = $RunspacePool 
       $Handle = $PowershellThread.BeginInvoke() 
       $Job = [pscustomobject][ordered]@{ 
        Handle = $Handle 
        Thread = $PowershellThread 
        object = $ItemObj.ToString() 
        Started = Get-Date 
       } 
       $Jobs += $Job 
      } 
     #} 
    } 
    End{ 
     $GlobalStartTime = Get-Date 
     $continue = $true 
     While (@($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0 -and $continue) { 
      ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){ 
       $out = $Job.Thread.EndInvoke($Job.Handle) 
       $out # return vers la sortie srandard 
       #Write-Host $out -ForegroundColor green 
       $Job.Thread.Dispose() | Out-Null 
       $Job.Thread = $Null 
       $Job.Handle = $Null 
      } 
      foreach ($InProgress in $($Jobs | Where-Object {$_.Handle})) { 
       if ($TimeOutGlobal -and (($(Get-Date) - $GlobalStartTime).totalseconds -gt $TimeOutGlobal)){ 
        $Continue = $false 
        #Write-Host $InProgress -ForegroundColor magenta 
       } 
       if (!$Continue -or ($TimeOutThread -and (($(Get-Date) - $InProgress.Started).totalseconds -gt $TimeOutThread))) { 
        $InProgress.thread.Stop() | Out-Null 
        $InProgress.thread.Dispose() | Out-Null 
        $InProgress.Thread = $Null 
        $InProgress.Handle = $Null 
        #Write-Host $InProgress -ForegroundColor red 
       } 
      } 
      Start-Sleep -Milliseconds $SleepTimer_ms 
     } 
     $RunspacePool.Close() | Out-Null 
     $RunspacePool.Dispose() | Out-Null 
    } 
} 
Cuestiones relacionadas