Débuter et comprendre les threads et les sémaphores

Tagged:

Voila je trouve ça suffisament important pour y consacrer un post bien construit alors je vais prendre en exemple la légendaire histoire des baigneurs et de la piscine avec suffisament peu de moyen pour ne disposer que d'un très petit nombre de cabines et de clients tous désireux d'avoir un panier ... perso jprends jamais de panier a la piscine mais bon là n'est pas le débat !!!

Si vous n'aimez pas le blahblah rendez vous sur la page de documentation et faites vous plaisir ;)

Bref le problème : Une piscine, un nombre maxCabines de cabines, un nombre maxPaniers de paniers et un nombre maxBaigneurs de baigneurs !!! Pour aller dans l'eau Il faut prendre un panier, puis on se dirige vers une cabine qu'on occupe un certain temps, on va dans la piscine un autre certain temps, on réoccupe une cabine pendant un certain temps, puis on rend le panier a la fin ... Naturellement si une cabine est occupé on ne doit pas pouvoir rentrer dedan !!!

Alors pourquoi les threads, pourquoi les semaphores et qu'est ce que c'est tous ces trucs ? Un thread est un procéssus qui tourne en parrallèle, en l'occurence les baigneurs vont tous en même temps se ruer dans la piscine et il faut donc qu'ils soient tous en éxucution parrallèlement ... Un sémaphore est en quelque sorte un vérrou ... Dans notre cas le sémaphore va être garent du nombre de panier et de cabines ... On va demander au sémaphore des panier, avant d'en prendre un, "puis-je prendre un panier", la le sémaphore nous réponds "vas-y" si oui et si non, il nous met en attente et finit par nous répondre "vas-y" quand un panier est rendu disponible ... Opération faite par le baigneur en disant au sémaphore "tiens j'en ai plus besoin ..."

Les "puis-je" et "vas-y" c'est ce qu'on trouve comme fonction le plus souvent dans la gestion de sémaphores ... P() et V() mais il existe aussi L() et U() pour lock et unlock, dans notre cas ce sera acquire et release !

Comment ça marche ? Définissons par exemple les bases :

#!/usr/bin/env python
 
from threading import BoundedSemaphore, Thread
from time import sleep
from random import random
 
maxBaigneurs = 10
maxCabines = 4
maxPaniers = 8
 
semPanier = BoundedSemaphore(maxPaniers)
semCabine = BoundedSemaphore(maxCabines)

On jouera avec les valeurs max à la fin pour observer le comportement des baigneurs !!!

Tiens en parlant des baigneurs définissons notre Baigneur !

class Baigneur(Thread):
 
        def __init__(self,id):
                Thread.__init__(self)
                self.id = id
 
        def run(self):
                self.prendrePanier()
                self.prendreCabine()
                self.seChanger()
                self.libererCabine()
                self.seBaigner()
                self.prendreCabine()
                self.seChanger()
                self.libererCabine()
                self.libererPanier()

Voila la on sait qu'un baigneur est un processus et on connais ses actions, il nous suffit alors de définir ce que le baigneur fait pendant ces actions ... voici celles associées aux sémaphores :

        def prendrePanier(self):
                semPanier.acquire()
                print self.id, "Prend un panier"
 
        def libererPanier(self):
                print self.id, "Libère un panier"
                semPanier.release()
 
        def prendreCabine(self):
                semCabine.acquire()
                print self.id, "Prend une cabine"
 
        def libererCabine(self):
                print self.id, "Libere cabine"
                semCabine.release()

Rien de bien méchant il suffit simplement de fair attention a où on écrit le print ... Dans la mesure ou un sémaphore est bloquant, si on écri print avant acquire, il va nous indiquer qu'il prend le panier mais il ne le prendra qu'un moment après ... pour le release ça n'a pas grande importance mais marquer avant, ça symbolise qu'on fait des actions avec le sémaphore encore bloqué et qu'on le libère à la fin !!!

Voyons maintenant les actions qui ne sont pas bien compliquées puisque on utilise les fonctions random.random() et time.sleep() qui font tout le boulot :

        def seChanger(self):
                t = int(random() * 5)
                print self.id, "Se change durant "+str(t)+" secondes"
                sleep(t)
 
        def seBaigner(self):
                t = int(random() * 10)
                print self.id, "Se baigne durant "+str(t)+" secondes"
                sleep(t)

Bon j'ai mis des temps significatifs si vous vous ennuyez lors de l'execution du programme il suffit de diminuer les multiplicateurs du random !

Voyons voir comment faire marcher tout ça ! Il suffit en fait de créer un tableau de baigneur et ensuite de leur dire a chacun de faire leur sauce et d'attendre qu'ils aient tous fini !!! Qu'a cela ne tienne :

baigneurs = [Baigneur(baigneur) for baigneur in range(maxBaigneurs)]
 
for baigneur in baigneurs:
        baigneur.start()
 
for baigneur in baigneurs:
        baigneur.join()

Voici un résultat avec 3 baigneurs, 2 paniers et 1 cabine :

0 Prend un panier
0 Prend une cabine
0 Se change durant 3 secondes
1 Prend un panier
0 Libere cabine
0 Se baigne durant 5 secondes
1 Prend une cabine
1 Se change durant 1 secondes
1 Libere cabine
1 Se baigne durant 3 secondes
1 Prend une cabine
1 Se change durant 2 secondes
1 Libere cabine
1 Libere un panier
0 Prend une cabine
0 Se change durant 1 secondes
2 Prend un panier
0 Libere cabine
0 Libere un panier
2 Prend une cabine
2 Se change durant 0 secondes
2 Libere cabine
2 Se baigne durant 1 secondes
2 Prend une cabine
2 Se change durant 2 secondes
2 Libere cabine
2 Libere un panier

On voit clairment que le 2 est obligé d'attendre la fin avant de se changer et de nager, on voit aussi que le 1 nage moins longtemps que le 0 donc il libère son panier avant !!!

Vous pouvez maintenant observer le fonctionnement de tout ça et agrémenter cet exemple pour comprendre comment fonctionne les process concurents !!! Personnellement j'ai fait cet exemple pour me remettre dans le bain et dans le but d'utiliser des buffer d'entré et sortie son en tant que serveurs XMLRPC threadés ... On peut bien sur tout faire, le python n'a de limites que celles qu'impose votre imagination ;)

Comments

Un exemple simple et bien expliqué. Pour une fois ça change de ce que l'on trouve sur la plupart des sites !

J'ai visité plusieurs sites avant d'arriver ici, et je trouve que c'est ce qu'il y’a de plus simple et claire. C’est vrai que ce n’est pas le plus complet, mais il faut déjà commencé par comprendre, et ça l’est. Merci

Merci pour cet exemple très clair, mais je ne comprends pas bien comment se comporte l'instruction baigneur.join().

Quelques infos supplémentaires?

La commande join se comporte comme ceci (c'est de la fiction hein ... c'est juste pour illustrer !)

while (baigneur.isBusy()) {wait()}

C'est pour imager mais grossomodo c'est l'idée ! =)

bonjour , cet exemple est bien explicatif. en effet il n'est pas de tout comliqué présenté de cette manière . je voudrais trouver une solution au problème des baigneurs en programmant sur c sous unix, j'ai trouvé beaucoup de difficultés , est ce que quelqu'un pourrait m'aider?

Vraiment pas mal pour un début...

La source fonctionne bien, y'a plus qu'à soigner les outputs(pour mon application) sans doute dans un threding à appartements.

et en ruby ça donne quoi?

Comment tu fais avec ce code puisque perso tu ne prends pas de panier ?

Qu'est-ce que quoi que quand ?

En introduction, tu disais que tu ne prenais pas de panier quand tu allais à la piscine.
La question est sérieuse, comment ton code pourrait offrir le passage optionnel de la prise de panier ?

ou encore, comment le distributeur de panier peut refuser un gars qui veut aller à la piscine pour lui faire sauter cette étape ?

merci infiniment
besoin de plus de solution des problème avec sémaphore où moniteur s.v.p

merci