Kurs
Wenn du gerade erst mit Python startest und mehr lernen willst, mach den Kurs Introduction to Data Science in Python von DataCamp.

Einführung
Threading ermöglicht es, verschiedene Teile eines Prozesses gleichzeitig auszuführen (Quelle: RealPython). Diese Teile sind in der Regel eigenständig und haben eine eigene Ausführungseinheit, gehören aber zum selben Prozess. Ein Prozess ist einfach ein laufendes Programm mit einzelnen Einheiten, die parallel laufen können. Beispiele: Ein Webbrowser ist ein Prozess, eine Anwendung, die mehrere Kameras gleichzeitig betreibt, ist ein Prozess; auch ein Videospiel ist ein Prozess.
Innerhalb eines Prozesses gibt es das Konzept des Multithreadings, bei dem mehrere Threads gemeinsam ein Ziel verfolgen. Der größte Vorteil von Threads ist, dass du Programmteile parallel laufen lassen kannst.
Verstehen wir Threading anhand eines Beispiels: Stell dir eine Anwendung vor, die die Anzahl der Autos zählt, die in das Parkhaus eines Einkaufszentrums ein- und ausfahren. Dein Aufbau umfasst mehrere Kameras, die Ein- und Ausfahrten überwachen und mit einem zentralen Gerät verbunden sind. Jede Kamera hat einen Algorithmus zur Überwachung des Verkehrsflusses, der zum selben Prozess gehört. Jede Kamera samt Algorithmus kann jedoch in einem eigenen Thread laufen. Sogar das Auslesen der Kameraframes und die Inferenz des Algorithmus auf diesen Frames können zwei getrennte Threads sein.
Ein weiteres Beispiel ist ein Videospiel, bei dem der Prozess Aufgaben parallel ausführen muss – etwa Grafik, Nutzerinteraktion und Netzwerkkommunikation (bei Multiplayer) – damit es jederzeit reaktionsschnell bleibt. Dafür nutzt man Multithreading, wobei jeder Thread eine unabhängige Aufgabe übernimmt.
Ein Thread hat seinen eigenen Ausführungsfluss, das heißt, im Prozess passieren mehrere Dinge gleichzeitig.
Wichtig: Jeder Prozess hat mindestens einen Thread, den main thread. Selbst wenn du keine zusätzlichen Threads definierst, existiert der Main-Thread – also der Prozess selbst.
Im obigen Diagramm siehst du einen Prozess mit dem Main-Thread (mit globalen Variablen) und drei Threads t1, t2 und t3. Jeder Thread hat eigene lokale Variablen und einen eigenen Ausführungsfluss. Die Threads teilen sich in der Regel die Ressourcen des Prozesses: Was dem Main-Thread zur Verfügung steht, wird auch mit t1, t2 und t3 geteilt. Alle Threads haben Zugriff auf globale Variablen und zusätzlich ihre lokalen.
Es gibt verschiedene Arten von Threads:
- Kernel-Thread
- User-Thread
- Kombination aus Kernel- und User-Thread
Schauen wir uns nun einige Vorteile von Threading in deinem Programm an:
-
Mit Multithreading kann ein Programm schneller ausführen – vorausgesetzt, es stehen mehrere CPUs zur Verfügung.
-
Du kannst andere Aufgaben erledigen, während I/O-Operationen laufen – mithilfe mehrerer Threads oder sogar mit dem Main-Thread plus einem zusätzlichen Thread. Beispiel: Das Auslesen der Kameraframes und die Inferenz des Algorithmus werden von unterschiedlichen Threads übernommen. So muss der Algorithmus nicht auf das nächste Frame warten, und das Auslesen blockiert nicht, bis die Inferenz beendet ist.
- Threads innerhalb desselben Prozesses können sich Speicher und Ressourcen des Main-Threads teilen.
Herausforderungen beim Threading
-
Python basiert in der Regel auf der CPython-Implementierung, die nur die Ausführung eines Threads zur selben Zeit erlaubt. Threading beschleunigt daher nicht jede Aufgabe. Der Hauptgrund dafür ist der Global Interpreter Lock (GIL).
Wenn du mehr über den GIL lernen willst, schau dir dieses Tutorial an.
-
Für CPU-intensive Aufgaben ist Threading meist nicht die beste Wahl. In solchen Fällen ist Multiprocessing oft sinnvoller.
-
Programme mit vielen Threads müssen schnell zwischen Threads wechseln. Deshalb ist sauberes Scheduling erforderlich, wenn du viele Threads einsetzt.
-
Ressourcenteilung kann problematisch sein, weil alle Threads denselben Speicher und die globalen Variablen teilen. Eine Operation in einem Thread kann Speicherfehler in einem anderen Thread verursachen, oder ein Thread bekommt nicht genug Speicher für seine Aufgabe.
Threading in Python
-
In Python gibt es das eingebaute Modul
threading, das du direkt importieren kannst. -
Da in Python fast alles ein Objekt ist, gilt das auch für Threads. Ein Thread kann
- Daten halten,
- in Datenstrukturen wie Dictionaries, Listen, Sets usw. gespeichert werden,
- als Parameter an eine Funktion übergeben werden.
-
Ein Thread kann auch als Prozess ausgeführt werden.
-
Ein Thread in Python kann verschiedene Zustände haben, z. B.:
- Warten,
- Gesperrt.
Schauen wir uns nun an, wie du Threading in Python umsetzt.
Thread-Modul in Python 3
Beachte: Python 3 ist abwärtskompatibel zum thread-Modul aus Python 2.7. In Python 3 kannst du es als _thread importieren. Sehen wir uns dazu ein Beispiel an und verstehen das _thread-Modul.
import _thread #thread module imported
import time #time module
Definieren wir eine Funktion thread_delay mit zwei Parametern: dem Namen des Threads und der Verzögerung. In dieser Funktion wirst du:
- Einen Zähler mit Null starten,
- Mit einer while-Schleife drei Durchläufe ausführen,
- Innerhalb der Schleife mit
time.sleep()eine Verzögerung einbauen, um die Thread-Ausführung sichtbar zu machen (in Sekunden), - Den Zähler um 1 erhöhen,
- Zur Kontrolle den Thread-Namen und die Zeit des jeweiligen Durchlaufs ausgeben.
def thread_delay(thread_name, delay):
count = 0
while count < 3:
time.sleep(delay)
count += 1
print(thread_name, '-------->', time.time())
Um die obige Funktion in einem Thread laufen zu lassen, verwendest du die Methode start_new_thread aus dem _thread-Modul.
Werfen wir einen Blick in die Docstring von start_new_thread.
?_thread.start_new_thread

Übergeben wir die Funktion thread_delay ohne Klammern und dazu die zwei Argumente, also Thread-Name und Verzögerung (damit du die sehr schnelle Thread-Ausführung sehen kannst).
_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 3))
10916
t1 --------> 1588077856.4221454
t1 --------> 1588077857.4418247
t2 --------> 1588077858.422453
t1 --------> 1588077858.4431
t2 --------> 1588077861.4371448
t2 --------> 1588077864.438915
Am Output siehst du: Der Thread t1 startet zuerst. t2 wartet wegen der 3-Sekunden-Verzögerung. Sobald diese abgelaufen ist, wird t2 ausgeführt – während t1 nur 1 Sekunde Verzögerung hat.
Ändern wir nun die Verzögerung von t2 auf 5 Sekunden. Dadurch startet t2 erst, wenn t1 bereits fertig ist, denn t1 benötigt nur 3 Sekunden.
_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 5))
14632
t1 --------> 1588078001.4087512
t1 --------> 1588078002.4272866
t1 --------> 1588078003.432218
t2 --------> 1588078005.4082909
t2 --------> 1588078010.4268317
t2 --------> 1588078015.4283602
Threading mit dem Threading-Modul umsetzen
Wir nutzen dasselbe Beispiel wie oben, diesmal aber mit dem Modul threading statt _thread.
import threading
import time
def thread_delay(thread_name, delay):
count = 0
while count < 3:
time.sleep(delay)
count += 1
print(thread_name, '-------->', time.time())
Im threading-Modul gibt es die Klasse Thread, die in ähnlichem Geist wie die Funktion start_new_thread des _thread-Moduls funktioniert.
Schauen wir in die Docstring der Klasse Thread, sie nimmt mehrere Parameter wie group, target (z. B. eine Funktion), args usw.
?threading.Thread

Im Konstruktor der Thread-Klasse übergibst du die Zielfunktion thread_delay und deren Argumente.
t1 = threading.Thread(target=thread_delay, args=('t1', 1))
t2 = threading.Thread(target=thread_delay, args=('t2', 3))
Um den Thread zu starten, verwendest du im threading-Modul die Methode start(), die den Thread ausführt.
?t1.start

t1.start()
t2.start()
t1 --------> 1588081121.7790291
t1 --------> 1588081122.7962046
t2 --------> 1588081123.7789369
t1 --------> 1588081123.7980876
t2 --------> 1588081126.7961557
t2 --------> 1588081129.7995002
Mit der Methode join wartest du, bis die Ausführung aller Threads abgeschlossen ist. Code nach join wird erst ausgeführt, wenn die Threads beendet sind.
?t1.join

t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
t1 --------> 1588081798.7118263
t1 --------> 1588081799.7231226
t2 --------> 1588081800.7129223
t1 --------> 1588081800.730732
t2 --------> 1588081803.7298765
t2 --------> 1588081806.7309563
Thread execution is complete!
Berechnen wir nun mit Threads das Volumen von Würfel und Quadrat – ganz unkompliziert.
def volume_cube(a):
print ("Volume of Cube:", a*a*a)
def volume_square(a):
print ("Volume of Square:", a*a)
t1 = threading.Thread(target=volume_cube, args=(2))
t2 = threading.Thread(target=volume_square, args=(3))
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Thread execution is complete!
Exception in thread Thread-9:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_square() argument after * must be an iterable, not int
Exception in thread Thread-8:
Traceback (most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: volume_cube() argument after * must be an iterable, not int
Moment – was ist da passiert? Direkt nach dem Start tritt ein Fehler auf. Grund: Der Parameter args erwartet ein Tuple. Da volume_cube und volume_square jeweils nur einen Parameter erwarten, musst du im args-Parameter nach dem ersten Argument ein Komma setzen.
t1 = threading.Thread(target=volume_cube, args=(2,))
t2 = threading.Thread(target=volume_square, args=(3,))
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Volume of Cube: 8
Volume of Square: 9
Thread execution is complete!
Als Nächstes nutzen wir Threading als Subklasse.
Threading als Subklasse
In diesem Abschnitt lernst du, wie du aus der Thread-Klasse im threading-Modul eine Subklasse erstellst und daraus die Thread-Instanzen t1 und t2 anlegst.
Auch hier verwenden wir die Funktion thread_delay.
class DataCampThread(threading.Thread):
def __init__(self, name, delay):
threading.Thread.__init__(self)
self.name = name
self.delay = delay
def run(self):
print('Starting Thread:', self.name)
thread_delay(self.name,self.delay)
print('Execution of Thread:', self.name, 'is complete!')
t1 = DataCampThread('t1', 1)
t2 = DataCampThread('t2', 3)
t1.start()
t2.start()
t1.join()
t2.join()
print("Thread execution is complete!")
Starting Thread: t1
Starting Thread: t2
t1 --------> 1588089083.4879806
t1 --------> 1588089084.489222
t1 --------> 1588089085.4909666
Execution of Thread: t1 is complete!
t2 --------> 1588089085.4979634
t2 --------> 1588089088.499365
t2 --------> 1588089091.5012596
Execution of Thread: t2 is complete!
Thread execution is complete!
Was passiert hier Schritt für Schritt?
-
Du definierst eine Klasse
DataCampThreadund gibst ihr als Basisklassethreading.Threadmit. Damit erstellst du eine Subklasse aus dem threading-Modul und kannst alle Methoden der Klassethreading.Threadwie bei Vererbung nutzen. -
Dann definierst du die Init-Methode der DataCampThread-Klasse und übergibst Name und Verzögerung. Das Init ist der Konstruktor – ähnlich den Parametern, die du der Klasse
threading.Threadübergibst, nur eben hier fürDataCampThread. -
Als Nächstes initialisierst du die Init-Methode der Basisklasse
threading.Threadund setzt Name und Verzögerung. -
Du definierst die Methode
run. Zur Erinnerung: Hier hast du die Methodestartverwendet, die unter der Hauberunaufruft. Da du die Basisklasse geerbt hast, darfst durunüberschreiben. Wenn du alsostartaufrufst, greifen deine Änderungen inrun.
Fazit
Glückwunsch – du hast das Tutorial abgeschlossen.
Dieses Tutorial war eine grundlegende Einführung in Threading in Python. Threading ist jedoch ein großes und wichtiges Thema, und einige Aspekte konnten wir hier nur anreißen. Sieh dir gern weitere Threading-Methoden wie active_count, current_thread usw. an.
Wenn du tiefer einsteigen willst, lies auch Pythons Doku zu Thread-basierter Parallelisierung.
Wenn du gerade erst mit Python beginnst und mehr lernen willst, mach den Kurs Introduction to Data Science in Python von DataCamp.