Je voudrais rendre une section de mon code plus efficace. Je pense à faire en sorte qu'il se déroule en plusieurs processus et de les exécuter 50/100 fois à la fois, au lieu d'une seule fois.
Par exemple (pseudo):
for line in file;
do
foo;
foo2;
foo3;
done
Je voudrais que la boucle s'exécute plusieurs fois. Je sais que cela peut être fait avec la fourche. Cela ressemblerait-il à quelque chose comme ça?
while(x <= 50)
parent(child pid)
{
fork child()
}
child
{
do
foo; foo2; foo3;
done
return child_pid()
}
Ou est-ce que je pense à ça dans le mauvais sens?
Merci!
Dans les scripts bash (non interactifs) par défaut, JOB CONTROL est désactivé, vous ne pouvez donc pas exécuter les commandes: job, fg et bg.
Voici ce qui fonctionne bien pour moi:
#!/bin/sh
set -m # Enable Job Control
for i in `seq 30`; do # start 30 jobs in parallel
sleep 3 &
done
# Wait for all parallel jobs to finish
while [ 1 ]; do fg 2> /dev/null; [ $? == 1 ] && break; done
La dernière ligne utilise "fg" pour mettre un travail d'arrière-plan au premier plan. Il le fait en boucle jusqu'à ce que fg renvoie 1 ($? == 1), ce qu'il fait lorsqu'il n'y a plus de tâches d'arrière-plan.
Je ne connais aucun appel explicite de fork
dans bash. Ce que vous voulez probablement faire, c'est ajouter &
à une commande que vous souhaitez exécuter en arrière-plan. Vous pouvez aussi utiliser &
sur les fonctions que vous définissez dans un script bash:
do_something_with_line()
{
line=$1
foo
foo2
foo3
}
for line in file
do
do_something_with_line $line &
done
MODIFIER : pour limiter le nombre de processus d'arrière-plan simultanés, vous pouvez essayer quelque chose comme ceci:
for line in file
do
while [`jobs | wc -l` -ge 50 ]
do
sleep 5
done
do_something_with_line $line &
done
Avec GNU Parallel vous pouvez faire:
cat file | parallel 'foo {}; foo2 {}; foo3 {}'
Cela exécutera un travail sur chaque cœur de processeur. Pour exécuter 50 do:
cat file | parallel -j 50 'foo {}; foo2 {}; foo3 {}'
Regardez les vidéos d'introduction pour en savoir plus:
Je n'aime pas utiliser wait
car il est bloqué jusqu'à la fin du processus, ce qui n'est pas idéal lorsqu'il y a plusieurs processus à attendre car je ne peux pas obtenir de mise à jour de l'état tant que le processus actuel n'est pas terminé. Je préfère utiliser une combinaison de kill -0
Et sleep
pour cela.
Étant donné un tableau de pids
à attendre, j'utilise la fonction waitPids()
ci-dessous pour obtenir une rétroaction continue sur les pids qui sont toujours en attente de terminer.
declare -a pids
waitPids() {
while [ ${#pids[@]} -ne 0 ]; do
echo "Waiting for pids: ${pids[@]}"
local range=$(eval echo {0..$((${#pids[@]}-1))})
local i
for i in $range; do
if ! kill -0 ${pids[$i]} 2> /dev/null; then
echo "Done -- ${pids[$i]}"
unset pids[$i]
fi
done
pids=("${pids[@]}") # Expunge nulls created by unset.
sleep 1
done
echo "Done!"
}
Lorsque je démarre un processus en arrière-plan, j'ajoute immédiatement son pid au tableau pids
en utilisant la fonction utilitaire ci-dessous:
addPid() {
desc=$1
pid=$2
echo "$desc -- $pid"
pids=(${pids[@]} $pid)
}
Voici un exemple qui montre comment utiliser:
for i in {2..5}; do
sleep $i &
addPid "Sleep for $i" $!
done
waitPids
Et voici à quoi ressemble la rétroaction:
Sleep for 2 -- 36271
Sleep for 3 -- 36272
Sleep for 4 -- 36273
Sleep for 5 -- 36274
Waiting for pids: 36271 36272 36273 36274
Waiting for pids: 36271 36272 36273 36274
Waiting for pids: 36271 36272 36273 36274
Done -- 36271
Waiting for pids: 36272 36273 36274
Done -- 36272
Waiting for pids: 36273 36274
Done -- 36273
Waiting for pids: 36274
Done -- 36274
Done!
l'approche de haridsv est excellente, elle donne la flexibilité d'exécuter une configuration d'emplacements de processeur où un certain nombre de processus peuvent être exécutés avec de nouveaux travaux soumis en tant que travaux terminés, en gardant la charge globale. Voici mes mods pour le code de haridsv pour un processeur à n emplacements pour une `` grille '' de `` tâches '' ngrid (je l'utilise pour les grilles de modèles de simulation) Suivi par une sortie de test pour 8 tâches 3 à la fois, avec des totaux cumulés de l'exécution , soumis, complété et restant
#!/bin/bash
########################################################################
# see haridsv on forking-multi-threaded-processes-bash
# loop over grid, submitting jobs in the background.
# As jobs complete new ones are set going to keep the number running
# up to n as much as possible, until it tapers off at the end.
#
# 8 jobs
ngrid=8
# 3 at a time
n=3
# running counts
running=0
completed=0
# previous values
prunning=0
pcompleted=0
#
########################################################################
# process monitoring functions
#
declare -a pids
#
function checkPids() {
echo ${#pids[@]}
if [ ${#pids[@]} -ne 0 ]
then
echo "Checking for pids: ${pids[@]}"
local range=$(eval echo {0..$((${#pids[@]}-1))})
local i
for i in $range; do
if ! kill -0 ${pids[$i]} 2> /dev/null; then
echo "Done -- ${pids[$i]}"
unset pids[$i]
completed=$(expr $completed + 1)
fi
done
pids=("${pids[@]}") # Expunge nulls created by unset.
running=$((${#pids[@]}))
echo "#PIDS :"$running
fi
}
#
function addPid() {
desc=$1
pid=$2
echo " ${desc} - "$pid
pids=(${pids[@]} $pid)
}
########################################################################
#
# Loop and report when job changes happen,
# keep going until all are completed.
#
idx=0
while [ $completed -lt ${ngrid} ]
do
#
if [ $running -lt $n ] && [ $idx -lt ${ngrid} ]
then
####################################################################
#
# submit a new process if less than n
# are running and we haven't finished...
#
# get desc for process
#
name="job_"${idx}
# background execution
sleep 3 &
addPid $name $!
idx=$(expr $idx + 1)
#
####################################################################
#
fi
#
checkPids
# if something changes...
if [ ${running} -gt ${prunning} ] || \
[ ${completed} -gt ${pcompleted} ]
then
remain=$(expr $ngrid - $completed)
echo " Running: "${running}" Submitted: "${idx}\
" Completed: "$completed" Remaining: "$remain
fi
# save counts to prev values
prunning=${running}
pcompleted=${completed}
#
sleep 1
#
done
#
########################################################################
Sortie de test:
job_0 - 75257
1
Checking for pids: 75257
#PIDS :1
Running: 1 Submitted: 1 Completed: 0 Remaining: 8
job_1 - 75262
2
Checking for pids: 75257 75262
#PIDS :2
Running: 2 Submitted: 2 Completed: 0 Remaining: 8
job_2 - 75267
3
Checking for pids: 75257 75262 75267
#PIDS :3
Running: 3 Submitted: 3 Completed: 0 Remaining: 8
3
Checking for pids: 75257 75262 75267
Done -- 75257
#PIDS :2
Running: 2 Submitted: 3 Completed: 1 Remaining: 7
job_3 - 75277
3
Checking for pids: 75262 75267 75277
Done -- 75262
#PIDS :2
Running: 2 Submitted: 4 Completed: 2 Remaining: 6
job_4 - 75283
3
Checking for pids: 75267 75277 75283
Done -- 75267
#PIDS :2
Running: 2 Submitted: 5 Completed: 3 Remaining: 5
job_5 - 75289
3
Checking for pids: 75277 75283 75289
#PIDS :3
Running: 3 Submitted: 6 Completed: 3 Remaining: 5
3
Checking for pids: 75277 75283 75289
Done -- 75277
#PIDS :2
Running: 2 Submitted: 6 Completed: 4 Remaining: 4
job_6 - 75298
3
Checking for pids: 75283 75289 75298
Done -- 75283
#PIDS :2
Running: 2 Submitted: 7 Completed: 5 Remaining: 3
job_7 - 75304
3
Checking for pids: 75289 75298 75304
Done -- 75289
#PIDS :2
Running: 2 Submitted: 8 Completed: 6 Remaining: 2
2
Checking for pids: 75298 75304
#PIDS :2
2
Checking for pids: 75298 75304
Done -- 75298
#PIDS :1
Running: 1 Submitted: 8 Completed: 7 Remaining: 1
1
Checking for pids: 75304
Done -- 75304
#PIDS :0
Running: 0 Submitted: 8 Completed: 8 Remaining: 0
Laissez-moi essayer l'exemple
for x in 1 2 3 ; do { echo a $x ; sleep 1 ; echo b $x ; } & done ; sleep 10
Et utilisez jobs
pour voir ce qui fonctionne.
Sur la base de ce que vous avez tous partagé, j'ai pu mettre cela ensemble:
#!/usr/bin/env bash
VAR1="192.168.1.20 192.168.1.126 192.168.1.36"
for a in $VAR1; do { ssh -t -t $a -l Administrator "Sudo softwareupdate -l"; } & done;
WAITPIDS="$WAITPIDS "$!;...; wait $WAITPIDS
echo "Script has finished"
Exit 1
Cela répertorie toutes les mises à jour sur le mac sur trois machines à la fois. Plus tard, je l'ai utilisé pour effectuer une mise à jour logicielle pour toutes les machines lorsque je CAT mon ipaddress.txt
Voici ma fonction de contrôle des threads:
#!/bin/bash
# This function just checks jobs in background, don't do more things.
# if jobs number is lower than MAX, then return to get more jobs;
# if jobs number is greater or equal to MAX, then wait, until someone finished.
# Usage:
# thread_max 8
# thread_max 0 # wait, until all jobs completed
thread_max() {
local CHECK_INTERVAL="3s"
local CUR_THREADS=
local MAX=
[[ $1 ]] && MAX=$1 || return 127
# reset MAX value, 0 is easy to remember
[ $MAX -eq 0 ] && {
MAX=1
DEBUG "waiting for all tasks finish"
}
while true; do
CUR_THREADS=`jobs -p | wc -w`
# workaround about jobs bug. If don't execute it explicitily,
# CUR_THREADS will stick at 1, even no jobs running anymore.
jobs &>/dev/null
DEBUG "current thread amount: $CUR_THREADS"
if [ $CUR_THREADS -ge $MAX ]; then
sleep $CHECK_INTERVAL
else
return 0
fi
done
}