Python-Multithreading
Multithreading ähnelt dem gleichzeitigen Ausführen mehrerer verschiedener Programme. Das Ausführen von Multithreads hat die folgenden Vorteile:
- Durch die Verwendung von Threads können Aufgaben, die in einem Programm viel Zeit beanspruchen, im Hintergrund verarbeitet werden.
- Die Benutzeroberfläche kann ansprechender gestaltet werden. Wenn ein Benutzer beispielsweise auf eine Schaltfläche klickt, um die Verarbeitung bestimmter Ereignisse auszulösen, kann ein Fortschrittsbalken eingeblendet werden, um den Verarbeitungsfortschritt anzuzeigen.
- Die Ausführungsgeschwindigkeit des Programms kann beschleunigt werden.
- Threads sind nützlicher für die Implementierung einiger Warteaufgaben wie Benutzereingabe, Dateilesen und -schreiben sowie Senden/Empfangen von Netzwerkdaten. In solchen Fällen können wir einige wertvolle Ressourcen wie den Speicherverbrauch freigeben.
Threads unterscheiden sich während der Ausführung immer noch von Prozessen. Jeder unabhängige Prozess hat einen Einstiegspunkt für die Programmausführung, eine sequenzielle Ausführungsreihenfolge und einen Ausgang für das Programm. Threads können jedoch nicht unabhängig ausgeführt werden; sie müssen von einer Anwendung abhängen, die mehrere Thread-Ausführungssteuerungen bereitstellt.
Jeder Thread hat seinen eigenen Satz von CPU-Registern, der als Kontext des Threads bezeichnet wird. Dieser Kontext spiegelt den Zustand der CPU-Register wider, als der Thread zuletzt ausgeführt wurde.
Die Register für den Befehlszeiger und den Stapelzeiger sind die zwei wichtigsten Register im Thread-Kontext. Ein Thread läuft immer im Kontext eines Prozesses, und diese Adressen werden verwendet, um Speicher im Adressraum des Prozesses zu identifizieren, dem der Thread gehört.
- Threads können verdrängt (unterbrochen) werden.
- Während andere Threads ausgeführt werden, kann ein Thread vorübergehend angehalten werden (auch Schlafen genannt) -- das ist Thread-Yielding.
Jetzt Python-Threads lernen
Es gibt zwei Möglichkeiten, Threads in Python zu verwenden: mit Funktionen oder indem eine Klasse verwendet wird, um Thread-Objekte zu umschließen.
Funktionaler Ansatz: Rufe die Funktion start_new_thread() im thread-Modul auf, um einen neuen Thread zu erstellen. Die Syntax lautet wie folgt:
thread.start_new_thread ( function, args[, kwargs] )
Parameter description:
- function - Die Thread-Funktion.
- args - Die an die Thread-Funktion übergebenen Argumente; es muss sich um einen Tupeltyp handeln.
- kwargs - Optionale Argumente.
Beispiel (Python 2.0+)
Die Ausgabe des oben genannten Programms lautet wie folgt:
Thread-1: Thu Jan 22 15:42:17 2009 Thread-1: Thu Jan 22 15:42:19 2009 Thread-2: Thu Jan 22 15:42:19 2009 Thread-1: Thu Jan 22 15:42:21 2009 Thread-2: Thu Jan 22 15:42:23 2009 Thread-1: Thu Jan 22 15:42:23 2009 Thread-1: Thu Jan 22 15:42:25 2009 Thread-2: Thu Jan 22 15:42:27 2009 Thread-2: Thu Jan 22 15:42:31 2009 Thread-2: Thu Jan 22 15:42:35 2009
Die Beendigung eines Threads beruht im Allgemeinen auf dem natürlichen Ende der Thread-Funktion; Sie können in der Thread-Funktion auch thread.exit() aufrufen, wodurch eine SystemExit-Ausnahme ausgelöst wird, um das Beenden des Threads zu erreichen.
Thread-Modul
Python bietet Unterstützung für Threads über zwei Standardbibliotheken, thread und threading. thread stellt Low-Level-Primitive-Threads und eine einfache Sperre bereit.
Andere Methoden, die vom threading-Modul bereitgestellt werden:
- threading.currentThread(): Gibt die aktuelle Thread-Variable zurück.
- threading.enumerate(): Gibt eine Liste mit laufenden Threads zurück. Laufend bedeutet nach dem Start des Threads und vor seinem Ende, ohne Threads vor dem Start und nach der Beendigung.
- threading.activeCount(): Gibt die Anzahl der laufenden Threads zurück, gleiches Ergebnis wie len(threading.enumerate()).
Neben der Verwendung von Methoden bietet das thread-Modul auch die Thread-Klasse zum Umgang mit Threads an. Die Thread-Klasse stellt die folgenden Methoden bereit:
- run():Methode zur Darstellung der Thread-Aktivität.
- start():Startet die Thread-Aktivität.
- join([time]):Wartet, bis der Thread beendet wird. Dies blockiert den aufrufenden Thread, bis die join()-Methode des Threads aufgerufen wird, um ihn zu beenden – normaler Exit oder eine unbehandelte Ausnahme – oder ein optionales Timeout auftritt.
- isAlive():Gibt zurück, ob der Thread aktiv ist.
- getName():Gibt den Thread-Namen zurück.
- setName():Legt den Thread-Namen fest.
Threads mit dem Threading-Modul erstellen
Um einen Thread mit dem Threading-Modul zu erstellen, erben Sie direkt von threading.Thread und überschreiben Sie dann die __init__-Methode und die run-Methode:
Beispiel (Python 2.0+)
Das Ergebnis der Ausführung des obigen Programms ist wie folgt:
Starting Thread-1 Starting Thread-2 Exiting Main Thread Thread-1: Thu Mar 21 09:10:03 2013 Thread-1: Thu Mar 21 09:10:04 2013 Thread-2: Thu Mar 21 09:10:04 2013 Thread-1: Thu Mar 21 09:10:05 2013 Thread-1: Thu Mar 21 09:10:06 2013 Thread-2: Thu Mar 21 09:10:06 2013 Thread-1: Thu Mar 21 09:10:07 2013 Exiting Thread-1 Thread-2: Thu Mar 21 09:10:08 2013 Thread-2: Thu Mar 21 09:10:10 2013 Thread-2: Thu Mar 21 09:10:12 2013 Exiting Thread-2
Thread-Synchronisation
Wenn mehrere Threads gemeinsam bestimmte Daten ändern, können unvorhersehbare Ergebnisse auftreten. Um die Datenkorrektheit sicherzustellen, müssen mehrere Threads synchronisiert werden.
Mit Lock und RLock von Thread-Objekten kann eine einfache Thread-Synchronisierung implementiert werden. Diese beiden Objekte besitzen sowohl acquire- als auch release-Methoden. Für Daten, bei denen zu einem Zeitpunkt nur ein Thread arbeiten darf, können die Operationen zwischen den acquire- und release-Methoden platziert werden. Wie folgt:
Der Vorteil von Multithreading besteht darin, dass mehrere Aufgaben gleichzeitig ausgeführt werden können (zumindest fühlt es sich so an). Wenn Threads jedoch Daten gemeinsam nutzen müssen, kann es zu Problemen mit der Daten-Desynchronisation kommen.
Betrachten Sie folgende Situation: Alle Elemente in einer Liste sind 0. Der Thread "set" setzt alle Elemente von hinten nach vorne auf 1, während der Thread "print" dafür verantwortlich ist, die Liste von vorne nach hinten zu lesen und auszugeben.
Dann ist es möglich, dass, wenn der Thread "set" mit dem Ändern beginnt, der Thread "print" die Liste ausgibt und die Ausgabe zur Hälfte aus Nullen und zur Hälfte aus Einsen besteht. Das ist Daten-Desynchronisation. Um diese Situation zu vermeiden, wird das Konzept der Sperren eingeführt.
Eine Sperre hat zwei Zustände – gesperrt und entsperrt. Immer wenn ein Thread wie "set" auf gemeinsame Daten zugreifen möchte, muss er zuerst die Sperre erwerben; wenn ein anderer Thread wie "print" die Sperre bereits erworben hat, dann soll der Thread "set" pausieren, was synchrones Blockieren ist; nachdem der Thread "print" den Zugriff beendet und die Sperre freigegeben hat, soll der Thread "set" fortfahren.
Mit einer solchen Behandlung wird beim Ausgeben der Liste entweder ausschließlich Nullen oder ausschließlich Einsen ausgegeben, und die unangenehme Situation von halb Nullen und halb Einsen tritt nicht mehr auf.
Beispiel (Python 2.0+)
Thread-Prioritätswarteschlange
Das Queue-Modul in Python bietet synchronisierte, thread-sichere Warteschlangenklassen, darunter die FIFO-Warteschlange (First-In-First-Out) Queue, die LIFO-Warteschlange (Last-In-First-Out) LifoQueue und die Prioritätswarteschlange PriorityQueue. Diese Warteschlangen implementieren alle Lock-Primitive und können direkt im Multithreading verwendet werden. Warteschlangen können verwendet werden, um die Synchronisation zwischen Threads zu erreichen.
Gängige Methoden im Queue-Modul:
- Queue.qsize() Gibt die Größe der Warteschlange zurück
- Queue.empty() Wenn die Warteschlange leer ist, gibt True zurück, andernfalls False
- Queue.full() Wenn die Warteschlange voll ist, gibt True zurück, andernfalls False
- Queue.full entspricht der Größe maxsize
- Queue.get([block[, timeout]]) Holt aus der Warteschlange, timeout ist die Wartezeit
- Queue.get_nowait() Entspricht Queue.get(False)
- Queue.put(item, block=True, timeout=None) Schreibt in die Warteschlange, timeout ist die Wartezeit
- Queue.put_nowait(item) Entspricht Queue.put(item, False)
- Queue.task_done() Nach Abschluss einer Arbeit sendet die Funktion Queue.task_done() ein Signal an die Warteschlange, dass die Aufgabe abgeschlossen wurde.
- Queue.join() Bedeutet eigentlich, zu warten, bis die Warteschlange leer ist, bevor andere Operationen ausgeführt werden.
Beispiel (Python 2.0+)
Das Ergebnis beim Ausführen des obigen Programms:
Starting Thread-1 Starting Thread-2 Starting Thread-3 Thread-1 processing One Thread-2 processing Two Thread-3 processing Three Thread-1 processing Four Thread-2 processing Five Exiting Thread-3 Exiting Thread-1 Exiting Thread-2 Exiting Main ThreadAndere Erweiterungen