[PYTHON] Ihr Threading.Event wird falsch verwendet

Einführung

Als ich mit Pythons threading.Event nach einem Beispiel gegoogelt wurde, fand ich eines, das oben missbraucht wurde. Ich bin in einer schlimmen Situation, in der alle drei Beiträge mit Threading.Event in Qiita missbraucht werden, daher erkläre ich, wie man sie richtig verwendet.

Was ist Threading.Event

Diese Klasse wird verwendet, um einen Thread warten zu lassen, bis ein Ereignis eintritt. Wenn ein Ereignis aus einem anderen Thread generiert wird, wird der wartende Thread fortgesetzt.

Die zwei wichtigsten Methoden sind:

Es gibt auch clear () und is_set (). Weitere Informationen finden Sie unter [Python-Dokumentation] threading.Event.

Richtige Benutzung

Lassen Sie uns zunächst sehen, wie Sie es richtig verwenden.

Einfaches Beispiel

Dies ist das einfachste Beispiel, bei dem nur wait () und set () verwendet werden. Der Thread wartet, bis das Ereignis eintritt, und der Haupt-Thread generiert das Ereignis 3 Sekunden nach dem Start des Threads.

Die erste Hälfte ist etwas lang, da die Zeit und der Threadname so eingestellt sind, dass sie in das Protokoll ausgegeben werden, aber keine Sorge.

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

#Protokolleinstellungen
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("Fadenstart")
    event.wait()
    logger.info("Fadenende")

thread = Thread(target=event_example1)
thread.start()
time.sleep(3)
logger.info("Ereigniseintritt")
event.set()

Ausführungsergebnis


[2016-09-27 00:04:18,400] [Thread-6]Fadenstart
[2016-09-27 00:04:21,406] [MainThread]Ereigniseintritt
[2016-09-27 00:04:21,407] [Thread-6]Fadenende

Wenn Sie sich die Zeit ansehen, können Sie sehen, dass der Thread auf das Eintreten des Ereignisses wartet.

Anruf warten mit Timeout

Wenn Sie ein Zeitlimit angeben, wird der Thread nach der angegebenen Anzahl von Sekunden fortgesetzt, auch wenn kein Ereignis auftritt. Der Rückgabewert von wait () ist True, wenn das Ereignis eintritt, andernfalls False.

python


event = Event()


def event_example2():
    logger.info("Fadenstart")
    while not event.wait(2):
        logger.info("Korrekt")
    logger.info("Fadenende")

thread = Thread(target=event_example2)
thread.start()
time.sleep(5)
logger.info("Ereigniseintritt")
event.set()

Ausführungsergebnis


[2016-09-27 00:04:21,407] [Thread-7]Fadenstart
[2016-09-27 00:04:23,412] [Thread-7]Korrekt
[2016-09-27 00:04:25,419] [Thread-7]Korrekt
[2016-09-27 00:04:26,409] [MainThread]Ereigniseintritt
[2016-09-27 00:04:26,409] [Thread-7]Fadenende

Sie können sehen, dass das Zeitlimit überschritten wird, auch wenn das Ereignis nicht auftritt. Wenn ein Ereignis auftritt, wird es vor dem Timeout neu gestartet.

Verwenden Sie Event wiederholt

Wie in der Dokumentation angegeben, kehrt der Thread nach dem Aufruf von set () zurück, auch wenn Sie wait () aufrufen, ohne zu warten. Wenn Sie Event wiederholt verwenden möchten, müssen Sie clear () aufrufen, um das Ereignis zu löschen.

Das Folgende ist ein Beispiel mit clear (). Es verwendet auch das Bool-Flag stop getrennt von event, um den Thread zu beenden.

python


event = Event()

#Ereignis-Stopp-Flag
stop = False


def event_example3():
    logger.info("Fadenstart")
    count = 0
    while not stop:
        event.wait()
        event.clear()
        count += 1
        logger.info(count)
    logger.info("Fadenende")

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()

Ausführungsergebnis


[2016-09-27 00:04:26,410] [Thread-8]Fadenstart
[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]Fadenende

Falsche Verwendung

Schauen wir uns als nächstes die falsche Verwendung an, die bei der Suche festgestellt wurde.

Fehlermuster 1 - Verwenden von Threading. Ereignis als nur eine Flagge

So beenden Sie einen Prozess mit mehreren Threads, die sich unendlich wiederholen - Qiita Betreiben Sie den laufenden Thread von außen - Qiita Multithread-Zundokokiyoshi mit Ereignis und Warteschlange - Qiita

Dieses Muster verwendet nur set () und is_set () und verwendet Event nur als Flag. Es mag übertrieben sein zu sagen, dass es falsch ist, aber es macht keinen Sinn, Event zu verwenden, es sei denn, Sie rufen wait () auf.

Beispiel für die Verwendung von Event als Flag


event = Event()


def bad_example1():
    logger.info("Fadenstart")
    while not event.is_set():
        logger.info("Korrekt")
        time.sleep(2)
    logger.info("Fadenende")

thread = Thread(target=bad_example1)
thread.start()
time.sleep(5)
logger.info("Ereigniseintritt")
event.set()

In solchen Fällen ist es einfacher, die Teile time.sleep () und is_set () im Thread durch Event.wait () zu ersetzen oder bool ohne Event zu verwenden.

wait()verwenden


event = Event()


def bad_example1():
    logger.info("Fadenstart")
    while not event.wait(timeout=2):
        logger.info("Korrekt")
    logger.info("Fadenende")

thread = Thread(target=bad_example1)
thread.start()
time.sleep(5)
logger.info("Ereigniseintritt")
event.set()

Verwenden Sie die Bool-Flagge


stop = False


def bad_example1():
    logger.info("Fadenstart")
    while not stop:
        logger.info("Korrekt")
        time.sleep(2)
    logger.info("Fadenende")

thread = Thread(target=bad_example1)
thread.start()
time.sleep(5)
logger.info("Ereigniseintritt")
stop = True

Fehlermuster 2-Vergessen klar ()

[Versuchen Sie 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)

Schauen Sie sich zunächst den obigen Beispiellink an.

Wenn in der BlockingQueue-Klasse die Warteschlange beim Aufruf von pop () leer ist, wartet sie, bis ein Ereignis eintritt, und in push () wird beim Hinzufügen eines Werts zur Warteschlange ein Ereignis generiert und der wartende Thread, der pop () aufgerufen hat, neu gestartet.

Consumer ruft wiederholt pop () auf und Producer ruft push () in Intervallen von 1 Sekunde auf, um der Warteschlange zufällige Werte hinzuzufügen.

Wenn Sie sich die Quelle ansehen, wird wait () verwendet, und selbst wenn sie ausgeführt wird, wird in Intervallen von 1 Sekunde ein zufälliger Wert angezeigt, sodass es so aussieht, als würde es das tun, was Sie erwarten.

Ausführungsergebnis


27
88
53
148
:

Wenn Sie jedoch ein Protokoll an die Stelle von wait () setzen, können Sie sehen, dass es sich lächerlich verhält.

    def pop(self):
        # wait()Schleife, um den verlassenen Thread zurückzugeben
        while True:
            #Holen Sie sich das Schloss
            with self.lock:
                if self.queue:
                    #Gibt alle Elemente in der Warteschlange zurück
                    return self.queue.pop()
                else:
                    print("waiting...")  # <---hinzufügen
                    #Wenn die Warteschlange leer ist, warten Sie, bis ein anderer Thread ein Element hinzufügt und Sie benachrichtigt
                    self.event.wait()

Ausführungsergebnis


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

Sie werden viel "Warten ..." sehen und Sie können sehen, dass die Bedrohung mit wait () überhaupt nicht wartet.

Ursache und Korrektur

Dies liegt daran, dass clear () nicht aufgerufen wird. Selbst wenn Sie wait () aufrufen, sobald Sie set () aufrufen, wird es nicht warten.

Damit dies ordnungsgemäß funktioniert, können Sie clear () aufrufen. Es gibt jedoch auch einen Fehler, dass es sich in einem toten Sperrzustand befindet, da es wartet (), während es die Sperre hält Musste repariert werden.

Überarbeitet


    def pop(self):
        # wait()Schleife, um den verlassenen Thread zurückzugeben
        while True:
            self.event.clear()
            #Holen Sie sich das Schloss
            with self.lock:
                if self.queue:
                    #Gibt alle Elemente in der Warteschlange zurück
                    return self.queue.pop()
            print "waiting..."
            #Wenn die Warteschlange leer ist, warten Sie, bis ein anderer Thread ein Element hinzufügt und Sie benachrichtigt
            self.event.wait()

Ausführungsergebnis


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

Jetzt wartet es richtig.

Apropos

Im Python-Standardmodul befindet sich eine [Warteschlange].

Dies ist eine Multithread-Warteschlange, mit der Sie:

--Warten Sie einen Thread, wenn die Warteschlange leer ist, wenn Sie pop () aufrufen.

In der BlockingQueue, die in diesem Beispiel angezeigt wird, wartet beim Aufrufen von "pop ()" auf einen Thread, wenn die Warteschlange leer ist. Dies wurde jedoch bereits mit dem Standardmodul erreicht.

Ich denke, dieses Beispiel wurde nur als Erklärung für das Ereignis geschrieben. Wenn Sie eine Warteschlange mit mehreren Threads verwenden möchten, verwenden Sie das Standardmodul [Warteschlange], anstatt es selbst zu erstellen.

Zusammenfassung

--threading.Event ist ohne wait () bedeutungslos

Recommended Posts

Ihr Threading.Event wird falsch verwendet
Verwendung von __dict__ in Python
Ihr mehrschichtiges Perceptron ist schmutzig
Was ist Ihr "Tanimoto-Koeffizient"?