Linux - Tirez parti du nombre de CPU dont vous disposez ; lancez des jobs concurrents
Optimisez vos scripts Bash ! Apprenez à lancer des jobs concurrents et à exploiter tous vos cœurs CPU avec les identifiants de processus (pids), wait et nproc pour réduire drastiquement le temps d'exécution.
Traduction automatique
Cet article a plus d'un an, mais il a été relu le 30 juillet 2026 — son contenu est toujours exact.
Cet article fait partie de la série Écrire de meilleurs scripts Bash :
- Bash - ASCII art
- Bash - Charger des variables d'environnement depuis un fichier
- Bash - Script pour ajouter des fonctionnalités de log à votre script
- Bash - Afficher sur la console et dans un fichier de log en même temps
- Linux - Comparer deux versions du même script
- Linux - Trier les fonctions dans un script Bash
- Linux - Générer de la documentation à partir de scripts Bash
- Linux - Tirez parti du nombre de CPU dont vous disposez ; lancez des jobs concurrents
- Linux - Utiliser une barre de progression dans vos scripts
- Linux - Scripts Bash - Lancer des tests unitaires avec bats/bats

Cet article montre comment exécuter des appels de fonctions Bash en parallèle plutôt qu'en séquence, en utilisant & pour passer chaque appel en arrière-plan, nproc pour dimensionner un pool de threads adapté aux cœurs CPU disponibles, et le suivi des identifiants de processus avec jobs | wc -l pour limiter le nombre d'exécutions simultanées — transformant une boucle séquentielle de 10 appels et 30 secondes en une version parallèle d'environ 3 secondes (avec une note pour respecter aussi les limites de débit imposées par l'API cible).
Dans mon activité professionnelle, j'ai été confronté au besoin suivant : traiter chaque ligne d'un fichier CSV et faire un appel API POST pour envoyer un document.
Une ligne du CSV contenait les informations à communiquer à un service API, et chaque ligne correspondait à un fichier PDF. Donc s'il y a 1000 lignes dans le fichier CSV, je dois faire 1000 appels API pour envoyer 1000 PDF.
J'ai écrit mon script en Bash sous Linux puis est venu le temps d'optimiser : pas un seul appel API à la fois, mais autant que possible.
Voyons comment lancer plus d'une tâche à la fois avec Bash sous Linux.
Prenons cette petite démo :
#!/bin/bashset -euo pipefail
function demo { echo "Sleeping for 3 seconds..." sleep 3 return 0}
function main { for i in {1..10}; do demo done}
start_time=$(date +%s)mainend_time=$(date +%s)total_time=$((end_time - start_time))echo "Total running time: $total_time seconds"Ce script bash exécute une fonction demo dix fois en boucle, chaque appel attendant 3 secondes. Il mesure le temps total d'exécution et l'affiche à la fin.
Avant d'appeler la fonction main, je retiens l'heure de départ réelle, j'appelle main puis je calcule le temps écoulé en secondes.
La fonction main est plutôt basique ici : je fais une boucle de 1 à 10 et, à chaque tour, j'appelle la fonction demo. Cette fonction attend 3 secondes.
Donc, en lançant le script, puisqu'on appelle notre fonction demo dix fois et que la fonction attend trois secondes, la durée totale n'a rien de surprenant :
Hé mec ! J'ai plus d'un CPU
Mais ce code est complètement dépassé, non ? Combien de CPU avais-je ? Un seul ? Oh merci les dieux de l'informatique, j'en ai bien plus que ça ! Alors, pourquoi n'en utiliser qu'un ?
Maintenant, la version optimisée
Ok, maintenant, on ne va plus lancer notre fonction de manière séquentielle mais l'appeler une fois et ne pas attendre la fin de la fonction : on continue la boucle et on appelle la même fonction une deuxième fois, une troisième fois, ...
Il faut juste s'assurer de ne pas tuer nos performances et, pour cela, on va récupérer le nombre de cœurs CPU de la machine. Sous Linux, c'est très simple : la commande nproc fait le travail.
Sur mon ordinateur, j'ai 32 processeurs logiques et comme je peux lancer 2 threads par CPU, je peux calculer le nombre maximum de threads comme ceci : NUMBER_OF_THREADS=$(( $(nproc) * 2 )).
Attention aussi aux limitations imposées par le tiers
Dans le script Bash ci-dessous, pas de souci, c'est juste mon ordinateur, mais dans mon introduction j'ai mentionné « je dois appeler un service API POST ». Ici, je m'assure simplement de ne pas être blacklisté par le serveur web. Par exemple, il y a peut-être une limitation du type « pas plus de 32 appels par seconde pour la même IP ». Dans ce cas, je dois en tenir compte et ne pas lancer plus de 32 processus à la fois (je fixerai alors NUMBER_OF_THREADS=32).
On va adapter notre exemple comme ceci :
#!/bin/bashset -euo pipefail
function demo { echo "Sleeping for 3 seconds..." sleep 3 return 0}
function main { # highlight-next-line # Array to store the PIDs of every jobs started in background # highlight-next-line declare -a pids # highlight-next-line pids=()
# highlight-next-line # Get the number of logical cores on the machine and multiply by 2 # highlight-next-line # So if the machine has 4 cores, we'll start 8 threads in the same time # highlight-next-line NUMBER_OF_THREADS=$(( $(nproc) * 2 )) # highlight-next-line
# highlight-next-line echo "Number of threads: ${NUMBER_OF_THREADS}" # highlight-next-line
for i in {1..10}; do # highlight-next-line # Run our function in the background; don't wait that the function finish # highlight-next-line demo &
# highlight-next-line # Remember the pid i.e. the ID of the function we've just started # highlight-next-line pids+=($!)
# highlight-next-line # As soon as we've started the max of concurrent threads; wait and allow all of them to finish. # highlight-next-line if [[ $( jobs | wc -l ) -ge ${NUMBER_OF_THREADS} ]]; then # highlight-next-line for pid in "${pids[@]}"; do # highlight-next-line wait "${pid}" # highlight-next-line done # highlight-next-line
# highlight-next-line # And reset the array since all jobs are done # highlight-next-line pids=() # highlight-next-line fi done
# highlight-next-line # And now, make sure all parallel jobs are finished # highlight-next-line for pid in "${pids[@]}"; do # highlight-next-line wait "${pid}" # highlight-next-line done}
start_time=$(date +%s)mainend_time=$(date +%s)total_time=$((end_time - start_time))echo "Total running time: $total_time seconds"Ce script bash lance 10 fonctions demo en parallèle, en limitant le nombre de tâches simultanées au double du nombre de cores disponibles sur la machine. Il attend que tous les processus se terminent avant de continuer, puis affiche le temps total d'exécution.
En détail
En ajoutant le caractère &, je demande à Bash de ne pas attendre que la fonction termine son travail avant de rendre la main. Donc, le code ci-dessous va exécuter demo 10 fois avant même que le premier appel à demo ne soit terminé.
for i in {1..10}; do
demo &
done
Si je dois faire 100, 500 ou 1 000 appels, mon ordinateur va commencer à peiner et, peut-être, ne plus répondre du tout. Mon ordinateur ne peut pas gérer 1 000 appels en même temps.
Que faire ?
Je sais que j'ai un nombre précis de cœurs (voir la commande nproc) et je sais que je peux lancer deux threads par cœur (donc, dans mon cas, je peux lancer 64 jobs en même temps).
Alors, avant de lancer l'appel suivant de demo, je dois faire deux choses :
- je garde l'identifiant de processus (
pid) de la fonctiondemoqui vient d'être lancée (chaque appel a son proprepid) et je stocke cette valeur dans un tableau, - je dois vérifier combien de jobs tournent et ne sont pas encore terminés (récupéré via
$( jobs | wc -l )) et comparer ce nombre au nombre de threads qu'on peut lancer (64 pour moi). Dès que le nombre de jobs en cours atteint mon maximum, ok, je dois attendre. Je ne dois pas lancer un 65e job mais attendre que les 64 précédents soient terminés.
Et, une fois que c'est fait, je peux continuer avec la vague suivante et ainsi de suite.
Enfin, après la boucle, je fais la même chose, c'est-à-dire m'assurer que tous les jobs définis dans notre tableau pids sont terminés.
Combien de secondes aurais-je attendu ?
Savez-vous combien de temps je dois attendre ? Rappelez-vous, dans la première version du script, j'ai attendu 30 secondes.
Avec la version optimisée ci-dessus et pour faire exactement la même chose, j'ai attendu ... trois secondes seulement :
Exécuter la fonction 50 fois
Dans la première version du script, en changeant la ligne for i in {1..10}; do en for i in {1..50}; do, j'attendrai 150 secondes ; non ? Avec la version optimisée, 4 secondes seulement. Pourquoi 4 et pas 3 ? Sans doute un léger délai introduit par le processeur (qui doit gérer 50 threads concurrents).
Comme les jobs se terminent maintenant dans le désordre, deux articles complémentaires deviennent utiles : Linux - Utiliser une barre de progression dans vos scripts pour garder un œil sur l'avancement global et Bash - Script pour ajouter des fonctionnalités de log à votre script pour savoir quel appel a échoué et quand.
Cet article fait partie de la série Écrire de meilleurs scripts Bash :
- Bash - ASCII art
- Bash - Charger des variables d'environnement depuis un fichier
- Bash - Script pour ajouter des fonctionnalités de log à votre script
- Bash - Afficher sur la console et dans un fichier de log en même temps
- Linux - Comparer deux versions du même script
- Linux - Trier les fonctions dans un script Bash
- Linux - Générer de la documentation à partir de scripts Bash
- Linux - Tirez parti du nombre de CPU dont vous disposez ; lancez des jobs concurrents
- Linux - Utiliser une barre de progression dans vos scripts
- Linux - Scripts Bash - Lancer des tests unitaires avec bats/bats
Chargement des commentaires…





