race condition

race condition

pdf pour impression, diaporama #

Exemples #

Les exemples sont sur le github NSI terminale

Race condition #

Race condition #

Situation crée quand plusieurs processus essayent d’accéder en même temps à une même ressource.

Définition #

Condition de compétition :

une situation caractérisée par un résultat différent selon l’ordre dans lequel agissent les acteurs du système.

Une situation de compétition peut survenir dès que plusieurs acteurs tentent d’accéder au même moment à une ressource partagée et que l’un d’entre eux est susceptible de modifier son état.

Les situations de compétition sont des problèmes particulièrement difficiles à identifier et à corriger puisqu’ils ne surviennent que suite à l’ordonnancement particulier et difficilement reproductible d’une séquence d’événements.

Notre exemple #

La ressource : Le programme doit accéder à celle-ci (fichier, bdd, imprimante…). Nous l’écran L’objectif du programme : écrire trois fois son numéro (pas trouvé plus simple) Résultat : si c’est 1 qui écrit en premier on sera dans l’état 1. Etc.

Principe #

On a un processeur (c’est déjà assez compliqué comme ça !).

Il fait UNE CHOSE À LA FOIS : cf assembleur (fetch, read, execute)

  • Comment donner l’illusion du multi tâche ?
  • Comment contrôler à l’avance un résultat ?

Exemple #

Deux scripts bash qui font la même chose : Ils écrivent dans la console le plus vite possible.

Le premier arrivé à trois affichages a gagné

c = 0
Tant que c < 3, faire :
    afficher MON NOM
    c = c + 1
Fin tant que

Exemple code bash #

contenu de runner_1.sh

#!/bin/sh

c=0 # compteur initialisé à 0
while ((c<3)); do # tant que compteur < 3
  echo "1" # écrit dans la console
  c=$c+1 # on augmente le compteur
done

Problème #

ils sont lancés en parallèle sur un même fil d’exécution avec : #

./runner_1.sh & ./runner_2.sh
  • Le & signifie : exécute et continue.

  • Ce qui est avant le & sera lancé et mis en fond de tâche.

  • Ce qui est après le & sera exécuté dans la foulée.

  • C’est le processeur qui décide quoi faire

    • Très difficile de savoir dans quel ordre il va exécuter ça !
    • C’est imprévisible (presque aléatoire)

Résultats parallèle : aléatoire #

Exécution 1      Exécution 2     Exécution 3
1                1               1
2                1               2
1                2               1
2                1               1
2                2               2
1                2               2

Exécution en série #

  • Il suffit de changer un symbole (& ---> ;) pour que l’exécution se

    fasse en série.

./runner_1.sh ; ./runner_2.sh

Résultat série : dans l’ordre de l’appel #

Exécution
1
1
1
2
2
2

Exécution avec des priorités #

La commande nice permet de donner plus ou moins de priorité à un processus.

nice -10 ./runner_1.sh & ./runner_2.sh

Attention : la valeur ici est 10. Plus elle est élevée moins c’est important.

Ils sont toujours lancés en parallèle mais runner_1 est moins important

Résultat priorité : dans l’ordre des priorités #

runner_1 est exécuté après runner_2

Execution
2
2
2
1
1
1

Pour changer la priorité et rendre PLUS important #

nice --5 ./runner_1.sh & ./runner_2.sh

La priorité de 1 est -5 : il est plus important

Résultat priorité : c’est 1 qui gagne #

runner_1 est exécuté avant runner_2

Execution
1
1
1
2
2
2

Résultat prévisible #

Comment s’assurer que deux processus lancés en même temps aient un résultat prévisible ?

nice la bonne idée ? #

Pas vraiment…

  • Par défaut il faut être super utilisateur pour donner la priorité maximale,
  • S’ils sont lancés en parallèle avec la même priorité ça redevient imprévisible,

Interblocage #

Une des solutions est de faire communiquer les processus entre eux. Ce n’est pas difficile mais ça demande d’avoir déjà compris les bases.

Une autre solution est de les empêcher de travailler s’ils n’ont pas accès à une ressource.

Par exemple en bloquant un dossier le temps de l’exécution d’un des processus.

Exemple #

On lance en parallèle avec :

./lockfile.sh 1 & ./lockfile.sh 2

Les numéros 1 et 2 sont des paramètres passés à chaque processus.

lockfile #

if ! mkdir /tmp/dossier.lock 2>/dev/null; then
    echo "Le processus tourne déjà !" >&2
    exit 1
fi
c=0
while (( c<3 )); do
  echo $1
  sleep 1
  c = $c + 1
done

locfile traduction #

essaye de créer le dossier /tmp/dossier.lock
si le dossier /tmp/dossier.lock existe déjà alors
  affiche une erreur et quitte le programme.

sinon
  faire trois fois :
    affiche ton numéro, dors une seconde
  fin boucle
fin si

Résultat #

1
Le processus tourne déjà !
1
1
  • Le processus 1 s’est lancé (il a crée le dossier), il a fonctionné.
  • Le processus 2 s’est lancé (impossible de créer une dossier existant), il a quitté.

Améliorer la méthode #

Au prix d’un peu plus de complexité dans le code, on peut s’assurer que les deux processus :

  1. soient lancés en parallèle,
  2. aient la même priorité,
  3. s’exécutent dans un ordre prévisible.

Conclusion #

  • Les processus sont lancés par l’utilisateur mais c’est le processeur qui décide de l’ordre dans lequel ils seront exécutés.
  • Deux processus lancés en parallèle s’exécutent dans des ordres difficiles à prédire,
  • Deux processus lancés en série s’exécutent l’un après l’autre,
  • Avec des priorités on peut contrôler… mais il faut savoir à l’avance quelle priorité donner.
  • En utilisant une ressource on peut éviter le problème.