[PYTHON] Votre threading.Event n'est pas utilisé correctement

introduction

Lorsque j'ai été recherché sur Google un échantillon qui utilise threading.Event de Python, j'en ai trouvé un qui était mal utilisé en haut. Je suis dans une situation désastreuse où les trois messages utilisant le threading.Event dans Qiita sont mal utilisés, alors je vais vous expliquer comment les utiliser correctement.

Qu'est-ce que threading.Event?

Cette classe est utilisée pour faire attendre un thread jusqu'à ce qu'un événement se produise, et lorsqu'un événement est généré à partir d'un autre thread, le thread en attente reprend.

Les deux méthodes les plus importantes sont:

Il existe également clear () et is_set (). Voir [documentation Python] threading.Event pour plus d'informations.

Usage correct

Voyons d'abord comment l'utiliser correctement.

Exemple simple

C'est l'exemple le plus simple utilisant uniquement wait () et set (). Le thread attend jusqu'à ce que l'événement se produise et le thread principal génère l'événement 3 secondes après le démarrage du thread.

La première moitié est un peu longue car l'heure et le nom du thread sont définis pour être affichés dans le journal, mais ne vous inquiétez pas.

from logging import (getLogger, StreamHandler, INFO, Formatter)

#Paramètres du journal
handler = StreamHandler()
handler.setLevel(INFO)
handler.setFormatter(Formatter("[%(asctime)s] [%(threadName)s] %(message)s"))
logger = getLogger()
logger.addHandler(handler)
logger.setLevel(INFO)


from threading import (Event, Thread)
import time


event = Event()


def event_example1():
    logger.info("Début du fil")
    event.wait()
    logger.info("Fin de fil")

thread = Thread(target=event_example1)
thread.start()
time.sleep(3)
logger.info("Occurrence d'événement")
event.set()

Résultat d'exécution


[2016-09-27 00:04:18,400] [Thread-6]Début du fil
[2016-09-27 00:04:21,406] [MainThread]Occurrence d'événement
[2016-09-27 00:04:21,407] [Thread-6]Fin de fil

Si vous regardez l'heure, vous pouvez voir que le thread attend que l'événement se produise.

Attente d'appel avec timeout

Si vous spécifiez un délai, le thread reprendra après le nombre de secondes spécifié, même si aucun événement ne se produit. La valeur de retour de wait () est True lorsque l'événement se produit, False sinon.

python


event = Event()


def event_example2():
    logger.info("Début du fil")
    while not event.wait(2):
        logger.info("C'est vrai")
    logger.info("Fin de fil")

thread = Thread(target=event_example2)
thread.start()
time.sleep(5)
logger.info("Occurrence d'événement")
event.set()

Résultat d'exécution


[2016-09-27 00:04:21,407] [Thread-7]Début du fil
[2016-09-27 00:04:23,412] [Thread-7]C'est vrai
[2016-09-27 00:04:25,419] [Thread-7]C'est vrai
[2016-09-27 00:04:26,409] [MainThread]Occurrence d'événement
[2016-09-27 00:04:26,409] [Thread-7]Fin de fil

Vous pouvez voir qu'il expire même si l'événement ne se produit pas. En outre, lorsqu'un événement se produit, il redémarre avant l'expiration du délai.

Utiliser l'événement à plusieurs reprises

Comme indiqué dans la documentation, une fois que vous appelez set (), même si vous appelez wait (), le thread reviendra sans attendre. Par conséquent, si vous souhaitez utiliser Event à plusieurs reprises, vous devez appeler clear () pour effacer l'événement.

Voici un exemple utilisant clear (). Il utilise également l'indicateur booléen stop séparément de ʻevent` pour terminer le thread.

python


event = Event()

#Indicateur d'arrêt d'événement
stop = False


def event_example3():
    logger.info("Début du fil")
    count = 0
    while not stop:
        event.wait()
        event.clear()
        count += 1
        logger.info(count)
    logger.info("Fin de fil")

thread = Thread(target=event_example3)
thread.start()

time.sleep(1)
event.set()
time.sleep(1)
event.set()
time.sleep(1)
stop = True
event.set()

thread.join()

Résultat d'exécution


[2016-09-27 00:04:26,410] [Thread-8]Début du fil
[2016-09-27 00:04:27,415] [Thread-8] 1
[2016-09-27 00:04:28,417] [Thread-8] 2
[2016-09-27 00:04:29,421] [Thread-8] 3
[2016-09-27 00:04:29,421] [Thread-8]Fin de fil

Mauvaise utilisation

Ensuite, regardons la mauvaise utilisation qui est ressortie de la recherche.

Erreur 1 - utilisation du filetage.Événement comme un simple drapeau

Comment terminer un processus avec plusieurs threads qui bouclent indéfiniment --Qiita Faites fonctionner le thread en cours d'exécution de l'extérieur --Qiita Zundokokiyoshi multithread utilisant Event and Queue --Qiita

Ce modèle utilise uniquement set () et is_set () et n'utilise Event que comme indicateur. Il peut être exagéré de dire que c'est faux, mais il ne sert à rien d'utiliser Event à moins d'appeler wait ().

Exemple d'utilisation de l'événement comme simple indicateur


event = Event()


def bad_example1():
    logger.info("Début du fil")
    while not event.is_set():
        logger.info("C'est vrai")
        time.sleep(2)
    logger.info("Fin de fil")

thread = Thread(target=bad_example1)
thread.start()
time.sleep(5)
logger.info("Occurrence d'événement")
event.set()

Dans ce cas, il est plus simple de remplacer les parties time.sleep () et is_set () dans le thread par Event.wait (), ou d'utiliser bool sans Event.

wait()utilisation


event = Event()


def bad_example1():
    logger.info("Début du fil")
    while not event.wait(timeout=2):
        logger.info("C'est vrai")
    logger.info("Fin de fil")

thread = Thread(target=bad_example1)
thread.start()
time.sleep(5)
logger.info("Occurrence d'événement")
event.set()

Utilisez l'indicateur booléen


stop = False


def bad_example1():
    logger.info("Début du fil")
    while not stop:
        logger.info("C'est vrai")
        time.sleep(2)
    logger.info("Fin de fil")

thread = Thread(target=bad_example1)
thread.start()
time.sleep(5)
logger.info("Occurrence d'événement")
stop = True

Motif d'erreur 2-oublié clair ()

[Essayez Python threading.Event | CUBE SUGAR STORAGE](http://momijiame.tumblr.com/post/38384408139/python-%E3%81%AE-threadingevent-%E3%82%92%E8% A9% A6% E3% 81% 97% E3% 81% A6% E3% 81% BF% E3% 82% 8B)

Tout d'abord, jetez un œil à l'exemple de lien ci-dessus.

Dans la classe BlockingQueue, si la file d'attente est vide lorsque pop () est appelée, elle attend qu'un événement se produise, et dans push (), lorsqu'une valeur est ajoutée à la file d'attente, un événement est généré et le thread en attente qui a appelé pop () est redémarré.

Le consommateur appelle à plusieurs reprises pop () et le producteur appelle push () à intervalles d'une seconde pour ajouter des valeurs aléatoires à la file d'attente.

En regardant la source, il utilise wait (), et même s'il est exécuté, une valeur aléatoire est affichée à 1 seconde d'intervalle, il semble donc qu'il fait ce que vous attendez.

Résultat d'exécution


27
88
53
148
:

Cependant, si vous mettez un journal à la place de wait (), vous pouvez voir qu'il se comporte de manière ridicule.

    def pop(self):
        # wait()Boucle pour retourner le thread qui s'est terminé
        while True:
            #Obtenez la serrure
            with self.lock:
                if self.queue:
                    #Renvoie tous les éléments de la file d'attente
                    return self.queue.pop()
                else:
                    print("waiting...")  # <---ajouter à
                    #Si la file d'attente est vide, attendez qu'un autre thread ajoute un élément et vous avertisse
                    self.event.wait()

Résultat d'exécution


waiting...
waiting...
waiting...
waiting...
:

Vous verrez beaucoup de "attente ..." et vous pouvez voir que la menace n'attend pas du tout avec wait ().

Cause et correction

C'est parce que clear () n'est pas appelé, donc même si vous appelez wait () une fois que vous appelez set (), il n'attendra pas.

Pour que cela fonctionne correctement, vous pouvez appeler clear (), mais il y a aussi un bogue selon lequel il sera dans un état de verrouillage mort car il attend () tout en maintenant le verrou, donc c'est comme suit Besoin d'être réparé.

modifié


    def pop(self):
        # wait()Boucle pour retourner le thread qui s'est terminé
        while True:
            self.event.clear()
            #Obtenez la serrure
            with self.lock:
                if self.queue:
                    #Renvoie tous les éléments de la file d'attente
                    return self.queue.pop()
            print "waiting..."
            #Si la file d'attente est vide, attendez qu'un autre thread ajoute un élément et vous avertisse
            self.event.wait()

Résultat d'exécution


7
waiting...
169
waiting...
113
waiting...

Maintenant, il attend correctement.

Au fait

Il y a une [file d'attente] dans le module standard Python.

Il s'agit d'une file d'attente multithread qui vous permet de:

--Attendez un thread si la file d'attente est vide lorsque vous appelez pop () --Si la file d'attente est pleine lorsque vous appelez push (), attendez qu'elle soit libre

Dans le BlockingQueue qui apparaît dans cet exemple, lorsque pop () est appelé, si la file d'attente est vide, elle attend un thread, mais cela a déjà été réalisé avec le module standard.

Je pense que cet exemple est juste écrit comme une explication de l'événement. Si vous souhaitez utiliser une file d'attente avec plusieurs threads, utilisez le module standard queue au lieu de le créer vous-même.

Résumé

--threading.Event n'a pas de sens sans utiliser wait () --Utilisez bool si vous souhaitez utiliser des indicateurs

Recommended Posts

Votre threading.Event n'est pas utilisé correctement
Comment utiliser __dict__ en Python
Votre Perceptron multicouche est sale
Quel est votre "coefficient de Tanimoto"?