Comment créer un sémaphore dans bash

Contenu

Bash Shell

Vous développez une application multithread? Tôt ou tard, vous aurez probablement besoin d'utiliser un feu de circulation. Dans cet article, vous apprendrez ce qu'est un feu de circulation, comment créer / pratiquer un en bash et plus.

Qu'est-ce qu'un Feux de circulation?

Un sémaphore est une construction de programmation utilisée dans les programmes informatiques qui utilisent plusieurs threads de traitement. (threads de traitement informatique exécutant le code source du même programme ou ensemble de programmes) obtenir l'utilisation exclusive d'une ressource commune à un moment donné. . Dit d'une manière beaucoup plus simple, pensez à cela comme “un à la fois, S'il vous plait”.

Un feu de circulation a été défini pour la première fois à la fin des années 1990. 1960 par le regretté informaticien Edsger Dijkstra de Rotterdam aux Pays-Bas. Vous avez probablement utilisé des feux de circulation fréquemment dans votre vie sans vous en rendre compte spécifiquement !!

Rester aux Pays-Bas pendant un certain temps, un pays plein de petits cours d'eau et de nombreux ponts mobiles (appelé pont levis en anglais américain), vous pouvez voir de nombreux exemples de feux de circulation dans le monde réel; considérer la poignée d'un opérateur de pont-levis: haut ou bas. Cette poignée est la variable de feu de circulation qui protège la voie navigable ou l'autoroute des accidents. A) Oui, la voie navigable et les routes pourraient être considérées comme d'autres variables protégées par le feu de circulation.

Si la poignée est relevée et le chevalet ouvert, l'usage exclusif de l'eau de l'intersection / la route est donnée au navire ou aux navires passant par le canal d'eau. Quand la poignée est baissée, le pont est fermé et l'usage exclusif de l'eau de l'intersection est donné / route aux voitures passant sur le pont.

La variable sémaphore peut contrôler l'accès à un autre ensemble de variables. Par exemple, l'état de la poignée empêche le $cars_per_minute et $car_toll_booth_total_income variables afin qu'elles ne soient pas mises à jour, etc.

On peut pousser l'exemple un peu plus loin et expliquer que lorsque la poignée fonctionne vers le haut ou vers le bas, les capitaines de bateaux dans l'eau et les personnes conduisant des voitures et des camions sur la route peuvent voir une lumière correspondante: tous les opérateurs peuvent lire une variable commune pour un état spécifique.

Même si le scénario décrit ici n'est pas seulement un feu de circulation, mais en même temps c'est un simple mutex. Un mutex est une autre construction de programmation commune qui est très similaire à un sémaphore, avec la condition supplémentaire qu'un mutex ne peut être déverrouillé que par la même tâche ou le même thread qui l'a verrouillé. Mutex signifie “mutuellement exclusifs”.

Pour ce cas, cela s'applique à notre exemple, puisque l'opérateur du pont est le seul à avoir le contrôle sur notre sémaphore et mutex up / vers le bas. Au contraire, si le poste de garde au bout du pont avait un interrupteur de contournement de pont, nous aurions toujours une configuration de sémaphore, mais pas un mutex.

Les deux constructions sont utilisées régulièrement dans la programmation informatique lors de l'utilisation de plusieurs threads pour garantir qu'une seule procédure ou tâche accède à une ressource donnée à un moment donné. Certains feux de circulation peuvent être à commutation ultra-rapide, par exemple, lorsqu'il est utilisé dans un logiciel de trading multithread sur les marchés financiers, et certains peuvent être beaucoup plus lents, ils ne changent d'état que toutes les quelques minutes, comme lorsqu'il est utilisé sur un pont-levis automatisé ou à un passage à niveau.

Maintenant que nous avons une meilleure compréhension des feux de circulation, implémentons-en un dans bash.

Mettre en place un feu de circulation dans Bash: C'est facile sur le?

L'implémentation d'un sémaphore dans Bash est si simple qu'elle peut même être effectuée directement à partir de la ligne de commande, Ou alors il semble …

Commençons simplement.

BRIDGE=up
if [ "${PONT}" = "En bas" ]; puis écho "Les voitures peuvent passer!"; autre écho "Les navires peuvent passer!"; fi
BRIDGE=down
if [ "${PONT}" = "En bas" ]; puis écho "Les voitures peuvent passer!"; autre écho "Les navires peuvent passer!"; être

Exemple de ligne de commande de mise à jour et de sortie de l’état du pont

Dans ce code, la variable BRIDGE a notre statut de pont. Quand nous le mettons dans up, les bateaux peuvent passer et quand nous le mettons dans down, les voitures peuvent passer. Al mismo tiempo podríamos leer el valor de nuestra variable en cualquier punto para ver si el puente está verdaderamente hacia arriba o hacia abajo. L’action / commun, dans cette circonstance, est notre pont.

Toutefois, cet exemple est à thread unique et, c'est à cause de ça, on ne trouve jamais commentaider les syndicats situation. Une autre façon de penser à cela est que notre variable ne peut jamais être up et down exactement en même temps que le code est exécuté séquentiellement, en d'autres termes, pas à pas.

Une autre chose à laquelle faire attention est que nous ne contrôlons pas vraiment l'accès à une autre variable (comme un feu de circulation le ferait régulièrement), Donc notre BRIDGE variable n'est pas vraiment une vraie variable sémaphore, même quand il se rapproche.

En résumé, dès que nous introduisons divers fils qui peuvent affecter le BRIDGE variable nous rencontrons des problèmes. Par exemple, et si, juste après BRIDGE=up commander, un autre problème de fil BRIDGE=down ce qui entraînerait plus tard le message Cars may pass! Sortir, même si le premier thread s'attendrait à ce que le pont soit up, et en fait le pont bouge toujours. Dangereux!

Vous pouvez voir comment les choses peuvent rapidement devenir troubles et confuses, sans parler du complexe, quand on travaille avec suffisamment de threads.

La situation où plusieurs threads essaient de mettre à jour la même variable en même temps ou au moins suffisamment près à temps pour qu'un autre thread gâche la situation (ce qui dans le cas des ponts-levis peut prendre beaucoup de temps) est appelé condition de course: deux threads en compétition pour mettre à jour ou signaler une variable ou un état, avec pour résultat qu'un ou plusieurs threads peuvent mal tourner.

Nous pouvons beaucoup améliorer ce code en le faisant circuler dans des procédures et en utilisant une vraie variable sémaphore qui restreindra l'accès à notre BRIDGE variable selon la situation.

Créer un sémaphore bash

Mettre en œuvre un multithread complet, rendre le thread sûr (une définition informatique pour décrire un logiciel qui est thread-safe ou développé de telle manière que les threads ne peuvent pas s'affecter négativement ou incorrectement alors qu'ils ne devraient pas) Ce n'est pas une tâche facile. Même un programme bien écrit qui utilise des sémaphores n'est pas garanti d'être absolument thread-safe..

Plus il y a de fils, et plus la fréquence et la complexité des interactions entre les threads sont élevées, les conditions de course sont plus probables.

Pour notre petit exemple, nous verrons la définition d'un sémaphore Bash lorsqu'un des opérateurs du pont-levis abaisse la poignée du pont, indiquant que vous voulez abaisser le pont. Les lecteurs avides ont peut-être remarqué la référence à opérateurs au lieu de opérateur: ahora hay varios operadores que pueden bajar el puente. En d'autres termes, hay varios subprocesos o tareas que se ejecutan al mismo tiempo.

#!/bin/bash

BRIDGE_SEMAPHORE=0

lower_bridge(){  # Un opérateur a placé l’une des poignées d’opération du pont vers le bas (en tant que nouvel État).
  # Supposons qu’il ait été préalablement convenu entre les opérateurs que dès que l’un des opérateurs 
  # déplace une poignée d’opération de pont dont la commande doit être exécutée, soit tôt ou tard
  # donc, we commence a loop which will wait for the bridge to become available for movement
  while true; do
    if [ "${BRIDGE_SEMAPHORE}" -Eq 1 ]; then
      echo "Sémaphore de pont verrouillé, déplacement de pont ou autre problème. Attente 2 quelques minutes avant de revérifier."
      dormir 120
      continuer  # Continue loop
    elif [ "${BRIDGE_SEMAPHORE}" -Eq 0 ]; then   
      echo "Commande de pont inférieur acceptée, verrouillage du sémaphore et abaissement du pont."
      BRIDGE_SEMAPHORE=1
      execute_lower_bridge
      wait_for_bridge_to_come_down
      BRIDGE='down'
      echo "Pont abaissé, s’assurer au moins 5 quelques minutes s’écoulent avant le prochain mouvement autorisé du pont."
      dormir 300
      écho "5 Minutes écoulées, déverrouillage du sémaphore (libération du contrôle du pont)"
      BRIDGE_SEMAPHORE=0
      break  # Exit loop
    fi
  done
}

Une mise en œuvre de feux de signalisation dans Bash

Ici, nous avons un lower_bridge fonction qui fera un certain nombre de choses. En premier lieu, supposons qu'un autre opérateur ait récemment déplacé le pont à la dernière minute. En tant que tel, il y a un autre thread qui exécute du code dans une fonction similaire à cet appel raise_bridge.

En réalité, cette fonction a fini de lever le pont, mais a institué une attente obligatoire de 5 minutes que tous les opérateurs ont convenu à l'avance et qui a été encodé dans le code source: empêcher le pont de monter / descendre tout le temps. En même temps, vous pouvez voir cette attente obligatoire de 5 minutes mises en œuvre dans cette fonction comme sleep 300.

Ensuite, quand cela raise_bridge la fonction fonctionne, aura défini la variable sémaphore BRIDGE_SEMAPHORE une 1, de la même manière que nous le faisons dans le code ici (juste après echo "Lower bridge command accepted, locking semaphore and lowering bridge" commander), et – à travers le premier if vérification conditionnelle dans ce code: la boucle infinie présente dans cette fonction continuera (réf continue Dans le code) boucler, avec des pauses de 2 minutes, comme lui BRIDGE_SEMAPHORE la variable est 1.

Dès que ça raise_bridge Le spectacle termine de lever le pont et termine sa suspension de cinq minutes, établira le BRIDGE_SEMAPHORE une 0, permettant à notre lower_bridge La fonction co commence à exécuter des fonctions execute_lower_bridge Et subséquente wait_for_bridge_to_come_down en même temps que nous avons de nouveau bloqué notre feu 1 pour empêcher d'autres fonctions de prendre le relais.

Toutefois, il y a des lacunes dans ce code et des conditions de concurrence possibles qui peuvent avoir des conséquences de grande envergure pour les opérateurs de ponts. Pouvez-vous voir n'importe quel?

Les "Lower bridge command accepted, locking semaphore and lowering bridge" pas thread-safe.

Si un autre fil, par exemple raise_bridge s'exécute en même temps et essaie d'accéder au BRIDGE_SEMAPHORE variable, il pourrait être (lorsque BRIDGE_SEMAPHORE=0 et les deux threads en cours d'exécution atteignent leur echoC'est exactement au même moment que les pontiers voient “La commande pont inférieur est acceptée, le feu de circulation est bloqué et le pont est abaissé” et “Commande de levage de pont acceptée, le feu de circulation est bloqué et le pont est surélevé”. Directement l'un après l'autre sur l'écran! Peur, non?

Encore plus effrayant est le fait que les deux fils peuvent continuer BRIDGE_SEMAPHORE=1, Et les deux threads peuvent continuer à s'exécuter! (Rien ne les empêche de le faire) La raison en est qu'il n'y a pas encore beaucoup de protection pour de tels scénarios. Même si ce code implémente un sémaphore, ce n'est en aucun cas thread-safe. Comme il a été dit, le codage multithread est complexe et nécessite beaucoup d'expérience.

Même si le temps requis dans cette circonstance est minime (1-2 les lignes de code ne prennent que quelques millisecondes à s'exécuter), et compte tenu du nombre probablement faible de pontiers, la chance que cela se produise est très faible. Toutefois, le fait qu'il soit viable est ce qui le rend dangereux. Créer du code thread-safe dans Bash n'est pas une tâche facile.

Cela pourrait être encore amélioré, par exemple, l'introduction d'un pré-verrouillage et / ou introduire une sorte de retard avec une nouvelle vérification ultérieure (même si cela nécessitera probablement une variable supplémentaire) ou faire une re-vérification régulière avant l'exécution réelle du pont. , etc. Une autre alternative consiste à créer une file d'attente prioritaire ou une variable de compteur qui vérifie combien de threads ont bloqué le contrôle du pont, etc.

Une autre approche couramment utilisée, par exemple, lors de l'exécution de plusieurs scripts bash susceptibles d'interagir, est d'utiliser mkdir O flock comme opérations de verrouillage de base. Il existe plusieurs exemples de mise en œuvre disponibles en ligne, par exemple, Quelles commandes Unix peuvent être utilisées comme sémaphore / blocage?.

Fin

Dans cet article, nous regardons ce qu'est un feu de circulation. En même temps, nous avons brièvement abordé le thème d'un mutex. En résumé, nous analysons la mise en œuvre d'un sémaphore dans Bash en utilisant l'exemple pratique de plusieurs opérateurs de pont exploitant un pont mobile / pont levis. Dans le même temps, nous explorons la complexité de la mise en œuvre d'une solution fiable basée sur les feux de circulation.

Si vous avez aimé lire cet article, jetez un oeil à nos revendications, erreurs et plantages: quelle est la différence? Article.

Abonnez-vous à notre newsletter

Nous ne vous enverrons pas de courrier SPAM. Nous le détestons autant que vous.