Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 15/05/2020, à 23:36

alex2423

Le fils le plus rapide donne des nouvelles à son père immédiatement

Hello tout le monde,

Mon titre n'est peut être pas très explicite.
C'est l'histoire d'un script père qui a 2 fils. Un des 2 fils est toujours plus rapide que l'autre pour faire ses taches.

Comment pour faire en sorte que le fils le plus rapide donne immédiatement des nouvelles à son père sans qu'il attende son frère, bien plus lent. Cela peut paraître un peu égoiste, désolé.

Le père et ses 2 fils :

 function fils1() {
	sleep 5
	echo "FIN FILS1"
}

function fils2() {
	sleep 10
	echo "FIN FILS2"
}
echo "PERE sous shell pere $BASH_SUBSHELL"
echo "PERE sous shell fils2 $BASHPID"
retour_fils1=$((fils1) &)
retour_fils2=$((fils2) &)

echo "Chez le pere : fils1 = ${retour_fils1}, fils2 = ${retour_fils2}"

Et voilà ce que j'obtiens :

xbionic@bionic:~/Documents/script$ bash -x multi_process.sh 
+ variable_globale=
+ echo 'PERE sous shell pere 0'
PERE sous shell pere 0
+ echo 'PERE sous shell fils2 5510'
PERE sous shell fils2 5510
++ fils1
++ sleep 5
++ echo 'FIN FILS1'
+ retour_fils1='FIN FILS1'     <==
++ fils2
++ sleep 10
++ echo 'FIN FILS2'
+ retour_fils2='FIN FILS2'
+ echo 'Chez le pere : fils1 = FIN FILS1, fils2 = FIN FILS2'
Chez le pere : fils1 = FIN FILS1, fils2 = FIN FILS2

Je m'attendais comme résultat
Chez le pere : fils1 = FIN FILS1, fils2 = FIN FILS2
Je m'attendais à ce que le fiston le plus rapide (ici "fils1") ayant terminé sa tache en premier, donne l'info immédiatement après à son père sans attendre fréro (ici "fils2") bien plus lent, pour communiquer tous les 2 l'info. Je m'attendais à ce que la variable ${retour_fils2} soit vide, non renseigné.

Hors ligne

#2 Le 16/05/2020, à 00:03

Zakhar

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

C'est un "bashism" qu'une fonction soit parallélisée ?

A mon sens ici tu n'as rien en parallèle...

Pour paralléliser il faut relancer ton script lui-même avec le & à la fin.
Aussi tu ne peux pas dans ce cas récupérer le résultat dans une variable puisque le "résultat" est en fait le stdout. Donc si tu attends le stdout du fils, de fait tu n'as plus de parallélisme.

Donc pour faire ce que tu désires, la sortie des fils doit être mis dans des fichiers, et il faut ensuite relire les fichiers pour voir ce qu'il y a dedans.

Après tu peux jouer avec les fonctions de "job" pour inspecter les fils terminés et ceux qui sont en cours et réagir en conséquence.

Bref : remaniement total du script !


"A computer is like air conditioning: it becomes useless when you open windows." (Linus Torvald)

Hors ligne

#3 Le 16/05/2020, à 08:45

alex2423

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Zakhar a écrit :

C'est un "bashism" qu'une fonction soit parallélisée ?

La fonction que je souhaite parallélisé est une fonction socle dans mon script, reprise plein de fois.

Rien empêche de créer des scripts externe et de "sourcer" (du style ". /mes_fonctions.sh") sur ma librairie maison mais je trouve que c'est ce compliqué la vie.


Zakhar a écrit :

Pour paralléliser il faut relancer ton script lui-même avec le & à la fin.
Aussi tu ne peux pas dans ce cas récupérer le résultat dans une variable puisque le "résultat" est en fait le stdout. Donc si tu attends le stdout du fils, de fait tu n'as plus de parallélisme.

Donc pour faire ce que tu désires, la sortie des fils doit être mis dans des fichiers, et il faut ensuite relire les fichiers pour voir ce qu'il y a dedans.

Okay, je comprends je n'avais pas la bonne logique.
Je créé des fils qui font leurs vies tout seul mais le point de blocage était au moment de l'affectation sur le processus père : retour_fils1=$((fils1) &)

D'ailleurs, je n'avais pas bien fait attention, en mode débug, on remarque que cela se bloque au niveau de l'affectation. La première affectation se déroule assez rapidement puis après il y a temporisation pour attendre l'affectation suivante pour le fils2.

Et après reflexion, cela me permet logique, une fois retourné chez le père, toutes les commandes synchrone et donc c'est normal que l'on attende le résultat des commandes les uns après les autres.

Il faudrait dans ce cas, créé les fils et c'est ensuite au père  de regarder l'état de ses fils, cela doit être de l'initiative du père. Et non pas l'inverse du fils au père

Comme tu me l'a conseillé, je suis donc passé par un fichier pour que le père puisse récupérer l'état des fils

Le nouveau code :

function fils1() {
	export FILS=FILS1
	sleep 5
	echo "FIN FILS1" 1>tmp/fils1	
}

function fils2() {
	export FILS=FILS2
	sleep 10
	echo "FIN FILS2" 1>tmp/fils1
	
}

(fils1) & 1>tmp/fils1
(fils2) & 1>tmp/fils2a


while true
do
	if [[ $(cat tmp/fils1) = "FIN FILS1" ]]
	then
		retourFils=$(cat tmp/fils1)
		echo "fin fils1, je sors"
		break
	elif [[ $(cat tmp/fils2) = "FIN FILS2" ]]
	then	
		retourFils=$(cat tmp/fils2)
		echo "fin fils2, je sors"
		break
	fi
done

echo "PERE : fils reour : $retourFils"
echo "variable en : $FILS"

Le code est donné pour exemple, utiliser des break par exemple n'est pas propre du tout.

Et voilà le résultat

xbionic@bionic:~/Documents/script/$ bash multi_process.sh 
fin fils1, je sors
PERE : fils reour : FIN FILS
variable en : 

On remarque cette fois-ci que l'on a tout de suite, le résultat de FILS1 et après on sort.

J'ai voulu tester avec les variables d'environnements mais malheureusement les sous shell des fils sont bien hermétiques.


Zakhar a écrit :

Après tu peux jouer avec les fonctions de "job" pour inspecter les fils terminés et ceux qui sont en cours et réagir en conséquence.

Cela pourrait être une solution plus propre mais malheureusement dans mon cas, je souhaite récupérer des coordonnées et celles-ci dépassent quasiment tout le temps les 255. Et je crois que l'on est limité à ce nombre dans le exit sad

Bref la seule est de passer par des fichiers, ce n'est pas très propre mais bon il semblerait que ce soit le seul moyen.

Hors ligne

#4 Le 16/05/2020, à 11:06

Hizoka

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Sinon, l'autre idée serait de passer par un FIFO.

Ton script principal lance les commandes en lisant les retours FIFO et les scripts appelés écrivent dessus.

En fonction de ce que lit le script principal, tu peux produire des actions genre :

while read Retour
do
case ${Retour} in
  bonjour) ... ;;
esac
done < fifo

KDE Neon 64bits
Tous mes softs (MKVExtractorQt, HizoSelect, HizoProgress, Qtesseract, Keneric, Services menus...) sont sur github

Hors ligne

#5 Le 18/05/2020, à 22:11

Zakhar

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Hizoka a écrit :

Sinon, l'autre idée serait de passer par un FIFO.

Effectivement, solution élégante si les fils écrivent des messages avec un retour à la ligne ! tongue


"A computer is like air conditioning: it becomes useless when you open windows." (Linus Torvald)

Hors ligne

#6 Le 18/05/2020, à 23:07

Hizoka

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Modifier l'IFS du while ne permettrait pas de régler ce problème ?


KDE Neon 64bits
Tous mes softs (MKVExtractorQt, HizoSelect, HizoProgress, Qtesseract, Keneric, Services menus...) sont sur github

Hors ligne

#7 Le 19/05/2020, à 08:04

marcus68

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Bonjour,

en m'inspirant des différents codes de ce fil, je vous propose cette solution (en bash) qui ne passe pas par des fichiers temporaires :

function fils1() {
	sleep 2
	echo "FIN FILS1"
}

function fils2() {
	sleep 5
	echo "FIN FILS2"
}

while read a
do
case "$a" in
	"FIN FILS1") echo "fils 1 plus rapide";break ;;
	"FIN FILS2") echo "fils 2 plus rapide";break ;;
esac
done <<< $(fils1&fils2)

Dernière modification par marcus68 (Le 19/05/2020, à 08:08)

Hors ligne

#8 Le 19/05/2020, à 09:19

Hizoka

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Avec ta façon, on attend que les 2 aient terminé.

Avec done < <(fils1&fils2), dés qu'un des 2 à terminé, il affiche le résultat.

Ça dépend des besoins smile

while read a
do
case "$a" in
	"FIN FILS1") echo "fils 1 terminé" ;;
	"FIN FILS2") echo "fils 2 terminé" ;;
esac
done < <(fils1&fils2)

Affichera fils 1 terminé puis fils 2 terminé.
Pratique pour traiter les retours au fur et à mesure.


KDE Neon 64bits
Tous mes softs (MKVExtractorQt, HizoSelect, HizoProgress, Qtesseract, Keneric, Services menus...) sont sur github

Hors ligne

#9 Le 19/05/2020, à 09:41

marcus68

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Le break interrompt le boucle, il s'arrête donc quand l'un ou l'autre renvoi le texte correspondant. Il n'y a donc pas de nécessité que les 2 aient fini pour que le script continue.

EDIT : non en effet, ça attends quand même

Dernière modification par marcus68 (Le 19/05/2020, à 09:51)

Hors ligne

#10 Le 19/05/2020, à 09:58

Hizoka

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

Hé hé hé smile

J'avais fait mon petit test car je n'étais pas sûr wink


KDE Neon 64bits
Tous mes softs (MKVExtractorQt, HizoSelect, HizoProgress, Qtesseract, Keneric, Services menus...) sont sur github

Hors ligne

#11 Le 24/05/2020, à 14:05

LeoMajor

Re : Le fils le plus rapide donne des nouvelles à son père immédiatement

bonjour

tu peux aussi remplacer le echo de fin de traitement par un signal, qui est récupéré par trap, ce qui se traduit par une simulation d'un évènement, d'un genre différent de while read

d'après une idée de @totor

cat timer.bash
#!/bin/bash 
#https://forum.ubuntu-fr.org/viewtopic.php?pid=21443151#p21443151
_SHELL_PID=
_SHELL_SIG=SIGUSR1
_SHELL_TIMESTAMP=60

function startTimer {
	while sleep ${_SHELL_TIMESTAMP}s
	do
		# on envoie le signal uniquement si le process existe toujours
		ps -p ${_SHELL_PID} &>/dev/null || return 0
		
		builtin kill -${_SHELL_SIG} ${_SHELL_PID}
#event=		raise ${_SHELL_PID} ${_SHELL_SIG} && action "$@"
	done
}

# vérification des paramétres
while getopts :s:p:t: option 
do
	case "${option}" in
		p) _SHELL_PID=${OPTARG};;
		s) _SHELL_SIG=${OPTARG};;		
		t) _SHELL_TIMESTAMP=${OPTARG};;
		:) echo "Argument manquant pour l'option '${OPTARG}'" >&2
			exit 1;;
		"?") echo "Option non valide : ${OPTARG}." >&2
			exit 1;;
	esac
done

# vérifications liées au PID
# par défaut, le PID est le PID du parent
[[ ${_SHELL_PID} ]] || _SHELL_PID=${PPID}

# vérification du format & de l'existence du process
[[ ${_SHELL_PID//[0-9]} ]] && {
	# erreur dans le format attendu
	echo "Numéro de PID '${_SHELL_PID}' erroné !" >&2
	exit 1
}

# le PID existe-t-il ?
ps -p ${_SHELL_PID} &>/dev/null || {
	echo "Aucun processus pour le PID '${_SHELL_PID}'" >&2
	exit 1
}	

# Vérification du signal à passer
[[ ${_SHELL_SIG} ]] || _SHELL_SIG=SIGUSR1

# précautions d'usage
_SHELL_SIG="${_SHELL_SIG^^}"
_SHELL_SIG="SIG${_SHELL_SIG#SIG}"

_SIG_LISTE="$(builtin kill -l)"
# on vérifie si c'est un signale valide
[[ "${_SIG_LISTE}" == *${_SHELL_SIG}\)* || "${_SIG_LISTE}" == *\)\ ${_SHELL_SIG}[[:space:]]* ]] || {
	echo "Signal '${_SHELL_SIG}' non valide !" >&2
	exit 1
}

# vérification du délai
# par défaut, le timestamp est de 60sec
[[ ${_SHELL_TIMESTAMP} ]] || _SHELL_TIMESTAMP=60
[[ ${_SHELL_TIMESTAMP//[0-9.]} ]] && {
	# erreur dans le format attendu
	echo "Délai '${_SHELL_TIMESTAMP}' non valide !" >&2
	exit 1
}
startTimer

cat  timer-raise-event

#!/bin/bash

trap event SIGUSR1
count=0
last=$count
limit=10  #nb de fois maximum que l'évènement se répète
timer_delay=0.5 # fréquence, sensibilité de l'evènement, en secondes

function event {
((count++))
echo "$count:signal:$(uuidgen)"

}

coproc TIMER { ./timer.bash -t "$timer_delay" -p $$ ; }
ps h -p $$ -o comm,cmd,ppid,pid
_PID=${TIMER_PID}
echo ${_PID}
ps h -p ${_PID} -o comm,cmd,ppid,pid
	
function eloop {

if [ "$limit" -lt 0 ]; then
echo "no limit"
while true; do if [ "x$last" != "x$count" ]; then last="$count"; echo "$last" ; fi; done; echo "no limit"

elif [ "$limit" -eq 1 ]; then
until [ "$count" -ge "$limit" ]; do if [ "x$last" != "x$count" ]; then last="$count"; echo "$last" ; fi; done; echo "oneshot"

elif [[ "$limit" =~ [0-9]+ ]]; then
until [ "$count" -ge "$limit" ]; do if [ "x$last" != "x$count" ]; then last="$count"; echo "$last" ; fi; done; echo "only $limit, events"

fi

}

eloop "$limit"
./timer-raise-event

dans une autre console

sudo forkstat

Hors ligne