Outils pour utilisateurs

Outils du site


thread_ordre

Faire agir des threads dans un ordre déterminé

Problématique

A priori, quand on lance des threads, on s'attend à ce qu'ils agissent sans qu'on puisse déterminer dans quel ordre ils vont le faire.

Dans certains cas, nous voulons au contraire qu'ils agissent dans un ordre bien déterminé.

Voilà un exemple de solution possible.

Solution avec un verrou (threading.Lock())

Le principe est simple:

  • on créé une variable globale “statut” initialisé à 0, et chaque thread pourra la lire et la modifier
  • on créé le verrou “vstatut” (threading.Lock()) qui ne permettra l'accès à la variable “statut” par les threads qu'un à la fois.
  • chaque thread attend, en lisant “statut”, que ce soit à son tour d'agir, et change “statut” à la fin pour que le suivant puisse agir à son tour.

Pour l'exemple:

  • j'ai créé 10 threads, et leur action consiste simplement à imprimer leur nom de thread sur la console.
  • pour que chacun imprime à son tour, j'ai ajouté un verrou vprint
  • les threads ne s'arrêtent que lorsque le programme principal leur demande

On voit bien dans l'impression de sortie que chaque thread agit bien à son tour, dans l'ordre des valeurs successives de “statut”.


#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import threading
import time
 
###############################################################################
class Acteur(threading.Thread):
 
    def __init__(self, nom, st1, st2):
        threading.Thread.__init__(self)
        self.setName(nom)
        self.st1 = st1  # = statut qui déclenche l'action
        self.st2 = st2  # = statut pour passer la main au thread suivant
        self.marche = True  # drapeau pour stopper le thread à la demande
 
    def run(self):
        global statut, vstatut, vprint
 
        while self.marche:
            # attente du bon statut avant l'action prévue:
            while True:
                vstatut.acquire()
                if statut==self.st1 or not self.marche:
                    vstatut.release()
                    break
                vstatut.release()
                time.sleep(0.1)
 
            # action prévue par le thread
            if self.marche:
                vprint.acquire()
                print "action faite par " + self.getName()
                vprint.release()
 
            # fin de l'action: le thread passe la main au suivant
            vstatut.acquire()
            statut=self.st2
            vstatut.release()
 
    def stop(self):
        self.marche = False
 
 
 
###############################################################################
 
print "debut"
 
# création de la variable globale partagée statut et de son verrou
statut = 0
vstatut = threading.Lock()
 
# création d'un verrou pour l'impression par les thread
vprint = threading.Lock()
 
# creation d'une liste de thread
nbacteurs = 10
acteurs = []
for i in xrange(0,nbacteurs):
    if i==(nbacteurs-1):
        k = 0
    else:
        k = i+1
    a = Acteur("A%d" % i, i, k)
    a.setDaemon(True)
    acteurs.append(a)
 
# lancement de tous les threads
for i in xrange(0,nbacteurs):
    acteurs[i].start()
 
# attente 5 secondes
time.sleep(5)
 
# arrêt de tous les threads
for i in xrange(0,nbacteurs):
    acteurs[i].stop()
 
# attente pour terminer que tous les threads soient arrêtés
for i in xrange(0,nbacteurs):
    acteurs[i].join()
    vprint.acquire()
    print "fin du thread " + acteurs[i].getName()
    vprint.release()
 
print "fini"

Ce programme affiche:

debut
action faite par A0
action faite par A1
action faite par A2
action faite par A3
action faite par A4
action faite par A5
action faite par A6
action faite par A7
action faite par A8
action faite par A9
action faite par A0
action faite par A1
action faite par A2
action faite par A3
action faite par A4
...
...
...
action faite par A5
action faite par A6
action faite par A7
action faite par A8
action faite par A9
action faite par A0
action faite par A1
action faite par A2
action faite par A3
stopper le thread A0
stopper le thread A1
stopper le thread A2
stopper le thread A3
stopper le thread A4
stopper le thread A5
stopper le thread A6
stopper le thread A7
stopper le thread A8
stopper le thread A9
fini

Solution avec une condition (threading.Condition())

C'est la même chose qu'un verrou, et cela affiche la même chose, à part qu'on peut espérer que l'attente de la condition (.wait()) consomme moins de ressources machine que la boucle while d'attente du bon statut.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import threading
import time
 
###############################################################################
class Acteur(threading.Thread):
 
    def __init__(self, nom, st1, st2):
        threading.Thread.__init__(self)
        self.setName(nom)
        self.st1 = st1  # = statut qui déclenche l'action
        self.st2 = st2  # = statut pour passer la main au thread suivant
        self.marche = True  # drapeau pour stopper le thread à la demande
 
    def run(self):
        global statut, cstatut, vprint
 
        while self.marche:
            # attente du bon statut avant l'action prévue:
            cstatut.acquire()
            if statut!=self.st1:
                cstatut.wait()
            cstatut.release()
            time.sleep(0.1)
 
            # action prévue par le thread
            if self.marche:
                vprint.acquire()
                print "action faite par " + self.getName()
                vprint.release()
 
            # fin de l'action: le thread passe la main au suivant
            cstatut.acquire()
            #print self.getName(), statut 
            statut=self.st2
            cstatut.notifyAll()
            cstatut.release()
 
    def stop(self):
        self.marche = False
 
###############################################################################
 
print "debut"
 
# création de la variable globale partagée statut et de son verrou
statut = 0
cstatut = threading.Condition()
 
# création d'un verrou pour l'impression par les thread
vprint = threading.Lock()
 
# creation d'une liste de thread
nbacteurs = 10
acteurs = []
for i in xrange(0,nbacteurs):
    if i==(nbacteurs-1):
        k = 0
    else:
        k = i+1
    a = Acteur("A%d" % i, i, k)
    a.setDaemon(True)
    acteurs.append(a)
 
# lancement de tous les threads
for i in xrange(0,nbacteurs):
    acteurs[i].start()
 
# attente 5 secondes
time.sleep(5)
 
# arrêt de tous les threads
for i in xrange(0,nbacteurs):
    acteurs[i].stop()
 
# attente pour terminer que tous les threads soient arrêtés
for i in xrange(0,nbacteurs):
    acteurs[i].join()
    vprint.acquire()
    print "fin du thread " + acteurs[i].getName()
    vprint.release()
 
print "fini"

thread_ordre.txt · Dernière modification: 2008/05/08 20:25 par tyrtamos

Outils de la page