Comme tout bon scripteur fou qui se respecte, vous avez créé de belles usines à gaz qui manipulent de gros volumes de données. Mais quand le traitement se compte en heures ou en jours, ça devient vraiment galère ! Et si on parallélisait les traitements ?

L’idée derrière le multi-threading est de découper le traitement en lots indépendants que l’on pourra exécuter en parallèle. A la fin, on consolide les résultats et c’est gagné 🙂

Un cas d’usage

Vous l’aurez compris, en prérequis il faut pouvoir décomposer les actions en groupes indépendants. Pour cela il faut que le traitement à paralléliser puisse être déplacé dans une fonction ou un script dédié. Prenons un exemple : vous extrayez la liste de vos site SharePoint et pour chaque site, vous voulez auditer les permissions. L’audit des permissions de chaque site est bien une actions indépendante pour chaque site. On a alors deux scripts:

  • l’un qui va auditer les permissions d’un site SharePoint et fournir un résultat au format CSV
  • l’autre qui servira de script chapeau pour
    • récupérer la liste des sites SharePoint
    • lancer les processus enfants pour l’analyse de chaque script (script précédent)
    • suivre le résultat d’exécution des processus enfants
    • consolider les résultats

Et en pratique ça donne quoi

Techniquement, il existe plusieurs méthode pour faire du multi-threading en PowerShell mais nous nous concentrerons sur la méthode la plus simple et la plus intégrée : Les PSJob. Ils sont intégrés directement depuis PowerShell version 3. L’autre méthode, les Runspaces sont plus efficace mais un peu plus complexes à mettre en œuvre et à gérer car il faut appeler les classes .NET en direct.
La dernière approche consiste à créer des scripts de type Workflow. Ils intègrent nativement des fonctions de traitement parallèle, mais cela nécessite de revoir un peu sa manière coder.

Première étape : je démarre les processus enfants

# Je créé la liste des arguments à passer (pour la lisibilité)
$myargs = @($param1Value, $param2Value)
# Je créé un nouveau job en appelant mon script avec ses paramètres
$newJob = start-job -filepath "$($currdir)\myChildScript.ps1" -argumentlist $myargs
# Je consolide les jobs dans une liste
$jobList += $newJob

Deuxième étape : on attend la fin de l’exécution des processus

# Wait for remaining jobs to finish
while (@($jobList |? {$_.state -like 'Running'}).count -gt 0) {
    $progress = @($jobList |? {$_.state -notlike 'Running'}).count
    Write-progress -activity "Processing Sites (with $maxThreadCount threads)" -status "Finishing threads" -PercentComplete ($progress*100/$urlList.count) -id 1
    start-sleep -seconds 10
}

Et pour finir, on regarde des processus qui on eut des erreurs

# Collect Errors logs
$errors = @($jobList |? {$_.state -like 'Failed'} |Receive-Job)
if ($errors.count -gt 0) {
    Write-log ("{0} thread failed: {1]" -f $errors.count, ($errors.id -join ',')) "ERROR"
}

Et pour aller plus loin

Si vous voulez limiter le nombre de processus qui s’exécuteront en parallèle, il faut se donner une limite. Grâce a elle on ajoute une condition afin de ne pas démarrer plus de processus que la limite tant que d’autres processus ne se sont pas terminés

if (@($jobList |? {$_.state -like 'Running'}).count -le $maxThreadCount) {
    # Je démarre mes processus enfants
} else {
    # MaxThreadCount reached
    start-sleep -seconds 1
}

Références

Cmd-let PowerShell :

Comparaison des méthodes de multi-threading PSJobs vs. Runspaces.

By Kevin

Leave a Reply

Your email address will not be published. Required fields are marked *