web-dev-qa-db-fra.com

Forking / Processus multithread | Frapper

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!

47
Greg

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.

51
Aleksandr Levchuk

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
28
mob

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:

http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

18
Ole Tange

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!
16
haridsv

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
3
Ralph

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.

3

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

2
Kamal

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
}
1
Drunkard