CodeGym /Java Kurs /Python SELF DE /Das Modul threading

Das Modul threading

Python SELF DE
Level 25 , Lektion 1
Verfügbar

2.1 Modul threading

Multithreading in Python bedeutet, dass mehrere Threads (threads) gleichzeitig ausgeführt werden können. Das ermöglicht eine effizientere Nutzung der Prozessor-Ressourcen, besonders bei I/O-Operationen oder anderen Aufgaben, die parallel ausgeführt werden können.

Grundlegende Begriffe des Multithreadings in Python:

Thread – die kleinste Ausführungseinheit, die parallel mit anderen Threads innerhalb eines Prozesses arbeiten kann. Alle Threads in einem Prozess teilen sich denselben Speicher, was den Datenaustausch zwischen den Threads ermöglicht.

Prozess – ein laufendes Programm in einem Betriebssystem mit einem eigenen Adressraum und eigenen Ressourcen. Im Gegensatz zu Threads sind Prozesse voneinander isoliert und tauschen Daten über Interprozesskommunikation (IPC) aus.

GIL – ein Mechanismus im Python-Interpreter, der verhindert, dass mehrere Python-Threads gleichzeitig ausgeführt werden. Das GIL stellt die Sicherheit bei der Ausführung von Python-Code sicher, begrenzt jedoch die Performance von multithreaded Programmen auf Mehrkern-Prozessoren.

Wichtig! Aufgrund des Global Interpreter Locks (GIL) sorgt Multithreading in Python möglicherweise nicht für eine deutliche Leistungssteigerung bei rechenintensiven Aufgaben, da das GIL die gleichzeitige Ausführung mehrerer Python-Threads auf Mehrkern-Prozessoren verhindert.

Das Modul threading

Das Modul threading in Python bietet eine High-Level-Schnittstelle für die Arbeit mit Threads. Es erlaubt das Erstellen und Verwalten von Threads, deren Synchronisation sowie die Organisation der Interaktion zwischen ihnen. Lass uns die wichtigsten Komponenten und Funktionen dieses Moduls näher anschauen.

Wichtige Komponenten des Moduls threading

Entitäten für die Arbeit mit Threads:

  • Thread – die Hauptklasse zum Erstellen und Verwalten von Threads.
  • Timer – ein Timer, der eine Funktion nach einer bestimmten Zeit ausführt.
  • ThreadLocal – ermöglicht das Erstellen von threadlokalen Daten.

Mechanismen zur Synchronisation von Threads:

  • Lock – ein primitiver Synchronisationsmechanismus, um gleichzeitigen Zugriff auf gemeinsame Ressourcen zu verhindern.
  • Condition – eine Bedingungsvariable für komplexere Thread-Synchronisationen.
  • Event – ein Mechanismus für die Benachrichtigung zwischen Threads.
  • Semaphore – ein Mechanismus zur Begrenzung der Anzahl der Threads, die gleichzeitig einen bestimmten Abschnitt ausführen können.
  • Barrier – synchronisiert eine bestimmte Anzahl von Threads, indem sie blockiert werden, bis alle den Barrierepunkt erreicht haben.

Im Folgenden erkläre ich dir drei Klassen zur Arbeit mit Threads; die Mechanismen zur Synchronisation von Threads wirst du in naher Zukunft wahrscheinlich nicht brauchen.

2.2 Klasse Thread

Die Klasse Thread ist die Hauptklasse zum Erstellen und Verwalten von Threads. Sie bietet vier Hauptmethoden:

  • start(): Startet die Ausführung des Threads.
  • join(): Der aktuelle Thread wird angehalten und wartet auf die Beendigung des gestarteten Threads.
  • is_alive(): Gibt True zurück, wenn der Thread aktiv ist.
  • run(): Eine Methode, die den Code enthält, der im Thread ausgeführt wird. Diese Methode wird beim Erben von der Klasse Thread überschrieben.

Alles ist tatsächlich einfacher, als es scheint – hier ein Beispiel für die Verwendung der Klasse Thread.

Start eines einfachen Threads


import threading

def worker():
    print("Worker thread is running")
            
# Erstellen eines neuen Threads
t = threading.Thread(target=worker) # Neuer Thread-Objekt erstellt
t.start() # Thread gestartet
t.join() # Warten auf das Ende des Threads
print("Main thread is finished")
        

Nach dem Aufruf der Methode start beginnt die Funktion worker mit ihrer Ausführung. Oder genauer gesagt: Ihr Thread wird der Liste der aktiven Threads hinzugefügt.

Verwendung von Argumenten


import threading

def worker(number, text):
    print(f"Worker {number}: {text}")
            
# Neuer Thread mit Argumenten
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
        

Um Parameter an einen neuen Thread zu übergeben, musst du sie lediglich als Tuple angeben und dem Parameter args zuweisen. Beim Aufruf der Ziel-Funktion, die im target angegeben wurde, werden die Parameter automatisch übergeben.

Überschreiben der Methode run


import threading

class MyThread(threading.Thread):
    def run(self):
        print("Custom thread is running")
            
# Erstellen und Starten eines Threads
t = MyThread()
t.start()
t.join()
        

Es gibt zwei Möglichkeiten, eine Funktion anzugeben, mit der die Ausführung eines neuen Threads beginnen soll: Entweder über den Parameter target beim Erstellen des Thread-Objekts oder durch Erben von der Klasse Thread und Überschreiben der Funktion run. Beide Ansätze sind legitim und werden häufig verwendet.

2.3 Klasse Timer

Die Klasse Timer im Modul threading dient dazu, eine Funktion nach einem bestimmten Zeitintervall aufzurufen. Diese Klasse ist nützlich, um verzögerte Aufgaben in einer Multithread-Umgebung auszuführen.

Der Timer wird mit der Funktion, die aufgerufen werden soll, und dem Verzögerungszeitpunkt in Sekunden initialisiert.

  • Methode start() startet den Timer, der das angegebene Zeitintervall zählt und dann die angegebene Funktion ausführt.
  • Methode cancel() ermöglicht das Stoppen des Timers, falls er noch nicht ausgelöst wurde. Das ist nützlich, um die Funktion nicht auszuführen, wenn der Timer nicht mehr benötigt wird.

Anwendungsbeispiele:

Funktion mit Verzögerung aufrufen

In diesem Beispiel wird die Funktion hello 5 Sekunden nach dem Start des Timers aufgerufen.


import threading

def hello():
    print("Hello, world!")
            
# Erstellen eines Timers, der die Funktion hello nach 5 Sekunden aufruft
t = threading.Timer(5.0, hello)
t.start()  # Timer starten
        

Timer vor der Ausführung stoppen

In diesem Fall wird der Timer gestoppt, bevor die Funktion hello ausgeführt werden kann; entsprechend wird nichts ausgegeben.


import threading

def hello():
    print("Hello, world!")
            
# Erstellen eines Timers
t = threading.Timer(5.0, hello)
t.start()  # Timer starten
            
# Timer vor der Ausführung stoppen
t.cancel()
        

Timer mit Argumenten

In diesem Beispiel ruft der Timer die Funktion greet nach 3 Sekunden auf und übergibt ihr das Argument "Alice".


import threading

def greet(name):
    print(f"Hello, {name}!")
            
# Erstellen eines Timers mit Argumenten
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
        

Die Klasse Timer ist praktisch für die Planung von Aufgaben, die nach einer bestimmten Zeit ausgeführt werden sollen. Allerdings garantieren Timer keine absolut präzise Ausführungszeit, da dies von der Systemlast und dem Thread-Scheduler abhängt.

2.4 Klasse ThreadLocal

Die Klasse ThreadLocal dient dazu, Threads mit eigenen lokalen Daten zu erstellen. Dies ist in Multithreaded-Anwendungen nützlich, wenn jeder Thread seine eigene Version von Daten haben muss, um Konflikte und Synchronisationsprobleme zu vermeiden.

Jeder Thread, der ThreadLocal verwendet, hat seine eigenen unabhängigen Kopien der Daten. Daten, die in einem ThreadLocal-Objekt gespeichert sind, sind für jeden Thread einzigartig und werden nicht mit anderen Threads geteilt. Das ist praktisch für die Speicherung von Daten, die nur im Kontext eines Threads verwendet werden, wie z. B. aktuelle Benutzer in Webanwendungen oder die aktuelle Verbindung zur Datenbank.

Anwendungsbeispiele:

Grundlegende Verwendung

In diesem Beispiel weist jeder Thread seinen Namen einer lokalen Variablen value zu und gibt diesen aus. Der Wert value ist für jeden Thread einzigartig.


import threading

# Erstellen eines ThreadLocal-Objekts
local_data = threading.local()
            
def process_data():
    # Wert der lokalen Thread-Variablen zuweisen
    local_data.value = threading.current_thread().name
    # Zugriff auf die lokale Thread-Variable
    print(f'Value in {threading.current_thread().name}: {local_data.value}')

threads = []
for i in range(5):
    t = threading.Thread(target=process_data)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Speicherung von Benutzerdaten in einer Webanwendung

In diesem Beispiel verarbeitet jeder Thread eine Anfrage für seinen Benutzer. Der Wert user_data.user ist für jeden Thread einzigartig.


import threading
# Erstellen eines ThreadLocal-Objekts
user_data = threading.local()
def process_request(user):
    # Wert der lokalen Thread-Variablen zuweisen
    user_data.user = user
    handle_request()

def handle_request():
    # Zugriff auf die lokale Thread-Variable
    print(f'Handling request for user: {user_data.user}')

threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
    t = threading.Thread(target=process_request, args=(user,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Das waren die drei nützlichsten Klassen der Bibliothek threading. Wahrscheinlich wirst du diese in deiner Arbeit verwenden, während die anderen Klassen weniger relevant sind. Heutzutage nutzen viele Leute lieber asynchrone Funktionen und die Bibliothek asyncio. Und genau darüber sprechen wir demnächst.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION