J'ai essayé de paralléliser le script suivant, en particulier chacune des trois instances de boucle FOR, en utilisant GNU Parallèle mais je n'ai pas pu. Les 4 commandes contenues dans la boucle FOR s'exécutent en série , chaque boucle prenant environ 10 minutes.
#!/bin/bash
kar='KAR5'
runList='run2 run3 run4'
mkdir normFunc
for run in $runList
do
fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12
fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12
fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
rm -f *.mat
done
Pourquoi ne les fourches-tu pas (aka. Background)?
foo () {
local run=$1
fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12
fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12
fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}
for run in $runList; do foo "$run" & done
Dans le cas où ce n'est pas clair, la partie importante est ici:
for run in $runList; do foo "$run" & done
^
Causer la fonction à exécuter dans un Shell fourchu en arrière-plan. C'est parallèle.
task(){
sleep 0.5; echo "$1";
}
for thing in a b c d e f g; do
task "$thing"
done
for thing in a b c d e f g; do
task "$thing" &
done
N=4
(
for thing in a b c d e f g; do
((i=i%N)); ((i++==0)) && wait
task "$thing" &
done
)
Il est également possible d'utiliser des FIFO comme sémaphores et de les utiliser pour garantir que les nouveaux processus sont générés dès que possible et que pas plus de N processus s'exécutent en même temps. Mais cela nécessite plus de code.
# initialize a semaphore with a given number of tokens
open_sem(){
mkfifo pipe-$$
exec 3<>pipe-$$
rm pipe-$$
local i=$1
for((;i>0;i--)); do
printf %s 000 >&3
done
}
# run the given command asynchronously and pop/Push tokens
run_with_lock(){
local x
# this read waits until there is something to read
read -u 3 -n 3 x && ((0==x)) || exit $x
(
( "$@"; )
# Push the return code of the command to the semaphore
printf '%.3d' $? >&3
)&
}
N=4
open_sem $N
for thing in {a..g}; do
run_with_lock task $thing
done
Nous utilisons le descripteur de fichier 3 comme sémaphore en poussant (= printf
) et en faisant apparaître (= read
) des jetons ('000'
). En poussant le code retour des tâches exécutées, nous pouvons abandonner en cas de problème.
for stuff in things
do
( something
with
stuff ) &
done
wait # for all the something with stuff
Que cela fonctionne réellement dépend de vos commandes; Je ne les connais pas. Le rm *.mat
semble un peu sujet aux conflits s'il s'exécute en parallèle ...
for stuff in things
do
sem -j+0 "something; \
with; \
stuff"
done
sem --wait
Cela utilisera des sémaphores, parallélisant autant d'itérations que le nombre de cœurs disponibles (-j +0 signifie que vous paralléliserez N + 0 travaux , où N est le nombre de cœurs disponibles ).
sem --wait indique d'attendre que toutes les itérations de la boucle for aient terminé leur exécution avant d'exécuter les lignes de code successives.
Remarque: vous aurez besoin de "parallèle" à partir du projet parallèle GN (Sudo apt-get install parallel).
Un moyen vraiment simple que j'utilise souvent:
cat "args" | xargs -P $NUM_PARALLEL command
Cela exécutera la commande, en passant dans chaque ligne du fichier "args", en parallèle, en exécutant au plus $ NUM_PARALLEL en même temps.
Vous pouvez également rechercher l'option -I pour xargs, si vous devez remplacer les arguments d'entrée à différents endroits.
Il semble que les tâches fsl dépendent les unes des autres, donc les 4 tâches ne peuvent pas être exécutées en parallèle. Cependant, les analyses peuvent être exécutées en parallèle.
Créez une fonction bash exécutant une seule exécution et exécutez cette fonction en parallèle:
#!/bin/bash
myfunc() {
run=$1
kar='KAR5'
mkdir normFunc
fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12
fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12
fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}
export -f myfunc
parallel myfunc ::: run2 run3 run4
Pour en savoir plus, regardez les vidéos d'introduction: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 et passez une heure à parcourir le didacticiel http: //www.gnu. org/software/parallel/parallel_tutorial.html Votre ligne de commande vous aimera pour cela.
#!/bin/bash
N=4
for i in {a..z}; do
(
# .. do your stuff here
echo "starting task $i.."
sleep $(( (RANDOM % 3) + 1))
) &
# allow only to execute $N jobs in parallel
if [[ $(jobs -r -p | wc -l) -gt $N ]]; then
# wait only for first job
wait -n
fi
done
# wait for pending jobs
wait
echo "all done"
J'aime vraiment la réponse de @lev car elle permet de contrôler le nombre maximum de processus d'une manière très simple. Cependant, comme décrit dans le manuel , sem ne fonctionne pas avec les crochets.
for stuff in things
do
sem -j +0 "something; \
with; \
stuff"
done
sem --wait
Fait le travail.
-j + N Ajoutez N au nombre de cœurs de processeur. Exécutez en parallèle ce nombre d'emplois. Pour les travaux intensifs en calcul, -j +0 est utile car il exécutera simultanément des travaux nombre de cpu-cores.
-j -N Soustraire N du nombre de cœurs de processeur. Exécutez en parallèle ce nombre d'emplois. Si le nombre évalué est inférieur à 1, 1 sera utilisé. Voir aussi --use-cpus-lieu-of-cores.
Dans mon cas, je ne peux pas utiliser de sémaphore (je suis en git-bash sur Windows), j'ai donc trouvé un moyen générique de répartir la tâche entre N travailleurs, avant qu'ils ne commencent.
Cela fonctionne bien si les tâches prennent à peu près le même temps. L'inconvénient est que, si l'un des travailleurs met beaucoup de temps à faire sa part de travail, les autres qui ont déjà terminé n'aideront pas.
# array of assets, assuming at least 1 item exists
listAssets=( {a..z} ) # example: a b c d .. z
# listAssets=( ~/"path with spaces/"*.txt ) # could be file paths
# replace with your task
task() { # $1 = idWorker, $2 = asset
echo "Worker $1: Asset '$2' START!"
# simulating a task that randomly takes 3-6 seconds
sleep $(( ($RANDOM % 4) + 3 ))
echo " Worker $1: Asset '$2' OK!"
}
nVirtualCores=$(nproc --all)
nWorkers=$(( $nVirtualCores * 1 )) # I want 1 process per core
worker() { # $1 = idWorker
echo "Worker $1 GO!"
idAsset=0
for asset in "${listAssets[@]}"; do
# split assets among workers (using modulo); each worker will go through
# the list and select the asset only if it belongs to that worker
(( idAsset % nWorkers == $1 )) && task $1 "$asset"
(( idAsset++ ))
done
echo " Worker $1 ALL DONE!"
}
for (( idWorker=0; idWorker<nWorkers; idWorker++ )); do
# start workers in parallel, use 1 process for each
worker $idWorker &
done
wait # until all workers are done
J'ai eu des problèmes avec la solution de @PSkocik
. Mon système n'a pas GNU Parallel disponible en tant que package et sem
a levé une exception lorsque je l'ai construit et exécuté manuellement. J'ai ensuite essayé l'exemple de sémaphore FIFO qui a également jeté quelques autres erreurs concernant la communication.
@eyeApps
A suggéré xargs mais je ne savais pas comment le faire fonctionner avec mon cas d'utilisation complexe (des exemples seraient les bienvenus).
Voici ma solution pour les travaux parallèles qui traitent jusqu'à N
travaux à la fois comme configuré par _jobs_set_max_parallel
:
_lib_jobs.sh:
function _jobs_get_count_e {
jobs -r | wc -l | tr -d " "
}
function _jobs_set_max_parallel {
g_jobs_max_jobs=$1
}
function _jobs_get_max_parallel_e {
[[ $g_jobs_max_jobs ]] && {
echo $g_jobs_max_jobs
echo 0
}
echo 1
}
function _jobs_is_parallel_available_r() {
(( $(_jobs_get_count_e) < $g_jobs_max_jobs )) &&
return 0
return 1
}
function _jobs_wait_parallel() {
# Sleep between available jobs
while true; do
_jobs_is_parallel_available_r &&
break
sleep 0.1s
done
}
function _jobs_wait() {
wait
}
Exemple d'utilisation:
#!/bin/bash
source "_lib_jobs.sh"
_jobs_set_max_parallel 3
# Run 10 jobs in parallel with varying amounts of work
for a in {1..10}; do
_jobs_wait_parallel
# Sleep between 1-2 seconds to simulate busy work
sleep_delay=$(echo "scale=1; $(shuf -i 10-20 -n 1)/10" | bc -l)
( ### ASYNC
echo $a
sleep ${sleep_delay}s
) &
done
# Visualize jobs
while true; do
n_jobs=$(_jobs_get_count_e)
[[ $n_jobs = 0 ]] &&
break
sleep 0.1s
done