Wie man in Python auf den Abschluss eines Threads wartet

PythonBeginner
Jetzt üben

Einführung

Das Beherrschen des Wartens auf das Ende von Python-Threads ist für die Entwicklung robuster und zuverlässiger Anwendungen unerlässlich. In Multithread-Programmen gewährleistet die richtige Synchronisation, dass Operationen in der korrekten Reihenfolge abgeschlossen werden und Ressourcen effizient genutzt werden.

In diesem Lab lernen Sie, wie man Python-Threads erstellt, auf deren Abschluss wartet und mehrere Threads handhabt. Diese Fähigkeiten sind grundlegend für die Entwicklung von nebenläufigen Anwendungen, die mehrere Aufgaben gleichzeitig ausführen können, während die richtige Synchronisation aufrechterhalten wird.

Erstellen Ihres ersten Python-Threads

Das threading-Modul von Python bietet eine einfache Möglichkeit, Threads zu erstellen und zu verwalten. In diesem Schritt lernen Sie, wie Sie einen einfachen Thread erstellen und sein Verhalten beobachten können.

Threads verstehen

Ein Thread ist ein separater Ausführungsfluss in einem Programm. Wenn Sie ein Python-Skript ausführen, beginnt es mit einem einzelnen Thread, dem sogenannten Haupt-Thread (main thread). Durch das Erstellen zusätzlicher Threads kann Ihr Programm mehrere Aufgaben gleichzeitig ausführen.

Threads sind nützlich für:

  • Ausführen zeitaufwändiger Operationen, ohne das Hauptprogramm zu blockieren
  • Verarbeiten von Aufgaben parallel zur Verbesserung der Leistung
  • Behandeln mehrerer Client-Verbindungen in einer Serveranwendung

Erstellen eines einfachen Threads

Beginnen wir mit der Erstellung eines einfachen Python-Skripts, das demonstriert, wie man einen Thread erstellt und startet.

  1. Öffnen Sie eine neue Datei im Editor, indem Sie auf das Menü "Datei" klicken, "Neue Datei" auswählen und sie dann als simple_thread.py im Verzeichnis /home/labex/project speichern.

  2. Fügen Sie der Datei den folgenden Code hinzu:

import threading
import time

def print_numbers():
    """Function that prints numbers from 1 to 5 with a delay."""
    for i in range(1, 6):
        print(f"Number {i} from thread")
        time.sleep(1)  ## Sleep for 1 second

## Create a thread that targets the print_numbers function
number_thread = threading.Thread(target=print_numbers)

## Start the thread
print("Starting the thread...")
number_thread.start()

## Main thread continues execution
print("Main thread continues to run...")
print("Main thread is doing other work...")

## Sleep for 2 seconds to demonstrate both threads running concurrently
time.sleep(2)
print("Main thread finished its work!")
  1. Speichern Sie die Datei, indem Sie Ctrl+S drücken oder auf "Datei" > "Speichern" klicken.

  2. Führen Sie das Skript aus, indem Sie ein Terminal öffnen (falls noch nicht geöffnet) und ausführen:

python3 /home/labex/project/simple_thread.py

Sie sollten eine Ausgabe ähnlich dieser sehen:

Starting the thread...
Main thread continues to run...
Main thread is doing other work...
Number 1 from thread
Number 2 from thread
Main thread finished its work!
Number 3 from thread
Number 4 from thread
Number 5 from thread

Analyse des Geschehens

In diesem Beispiel:

  1. Wir haben die Module threading und time importiert.
  2. Wir haben eine Funktion print_numbers() definiert, die Zahlen von 1 bis 5 mit einer Verzögerung von 1 Sekunde zwischen jeder Zahl ausgibt.
  3. Wir haben ein Thread-Objekt erstellt und die auszuführende Funktion mit dem Parameter target angegeben.
  4. Wir haben den Thread mit der Methode start() gestartet.
  5. Der Haupt-Thread setzte seine Ausführung fort, gab Nachrichten aus und schlief für 2 Sekunden.
  6. Sowohl der Haupt-Thread als auch unser Zahlen-Thread liefen gleichzeitig, weshalb die Ausgabe verschachtelt ist.

Beachten Sie, dass der Haupt-Thread beendet wurde, bevor der Zahlen-Thread alle seine Zahlen ausgab. Dies liegt daran, dass Threads unabhängig voneinander ausgeführt werden, und standardmäßig beendet sich das Python-Programm, wenn der Haupt-Thread beendet ist, auch wenn andere Threads noch laufen.

Im nächsten Schritt lernen Sie, wie Sie mit der Methode join() auf den Abschluss eines Threads warten können.

Auf den Abschluss eines Threads mit join() warten

Im vorherigen Schritt haben Sie einen Thread erstellt, der unabhängig vom Haupt-Thread ausgeführt wurde. Es gibt jedoch viele Situationen, in denen Sie warten müssen, bis ein Thread seine Arbeit beendet hat, bevor Sie mit dem Rest Ihres Programms fortfahren. Hier ist die join()-Methode nützlich.

Die join()-Methode verstehen

Die join()-Methode eines Thread-Objekts blockiert den aufrufenden Thread (normalerweise den Haupt-Thread), bis der Thread, dessen join()-Methode aufgerufen wird, beendet wird. Dies ist unerlässlich, wenn:

  • Der Haupt-Thread Ergebnisse von einem Worker-Thread benötigt
  • Sie sicherstellen müssen, dass alle Threads abgeschlossen sind, bevor das Programm beendet wird
  • Die Reihenfolge der Operationen für Ihre Anwendungslogik wichtig ist

Erstellen eines Threads und Warten auf dessen Abschluss

Ändern wir unser vorheriges Beispiel, um zu demonstrieren, wie man mit der join()-Methode auf den Abschluss eines Threads wartet.

  1. Erstellen Sie eine neue Datei mit dem Namen join_thread.py im Verzeichnis /home/labex/project.

  2. Fügen Sie der Datei den folgenden Code hinzu:

import threading
import time

def calculate_sum(numbers):
    """Function that calculates the sum of numbers with a delay."""
    print("Starting the calculation...")
    time.sleep(3)  ## Simulate a time-consuming calculation
    result = sum(numbers)
    print(f"Calculation result: {result}")
    return result

## Create a list of numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

## Create a thread that targets the calculate_sum function
calculation_thread = threading.Thread(target=calculate_sum, args=(numbers,))

## Start the thread
print("Main thread: Starting the calculation thread...")
calculation_thread.start()

## Do some other work in the main thread
print("Main thread: Doing some other work while waiting...")
for i in range(5):
    print(f"Main thread: Working... ({i+1}/5)")
    time.sleep(0.5)

## Wait for the calculation thread to complete
print("Main thread: Waiting for the calculation thread to finish...")
calculation_thread.join()
print("Main thread: Calculation thread has finished!")

## Continue with the main thread
print("Main thread: Continuing with the rest of the program...")
  1. Speichern Sie die Datei und führen Sie sie mit dem folgenden Befehl aus:
python3 /home/labex/project/join_thread.py

Sie sollten eine Ausgabe ähnlich dieser sehen:

Main thread: Starting the calculation thread...
Starting the calculation...
Main thread: Doing some other work while waiting...
Main thread: Working... (1/5)
Main thread: Working... (2/5)
Main thread: Working... (3/5)
Main thread: Working... (4/5)
Main thread: Working... (5/5)
Main thread: Waiting for the calculation thread to finish...
Calculation result: 55
Main thread: Calculation thread has finished!
Main thread: Continuing with the rest of the program...

Die Bedeutung von join()

In diesem Beispiel:

  1. Wir haben einen Thread erstellt, der eine Berechnung (Summieren von Zahlen) durchführt.
  2. Der Haupt-Thread hat parallel dazu andere Arbeiten erledigt.
  3. Wenn der Haupt-Thread sicherstellen musste, dass die Berechnung abgeschlossen war, rief er calculation_thread.join() auf.
  4. Die join()-Methode bewirkte, dass der Haupt-Thread wartete, bis der Berechnungs-Thread fertig war.
  5. Nachdem der Berechnungs-Thread abgeschlossen war, setzte der Haupt-Thread seine Ausführung fort.

Dieses Muster ist sehr nützlich, wenn Sie sicherstellen müssen, dass alle Thread-Aufgaben abgeschlossen sind, bevor Sie mit dem Rest Ihres Programms fortfahren. Ohne join() könnte der Haupt-Thread fortfahren und sogar beendet werden, bevor die Worker-Threads ihre Aufgaben erledigt haben.

Verwenden von join() mit einem Timeout

Manchmal möchten Sie möglicherweise auf einen Thread warten, aber nicht unbegrenzt. Die join()-Methode akzeptiert einen optionalen Timeout-Parameter, der die maximale Anzahl von Sekunden angibt, die gewartet werden soll.

Ändern wir unseren Code, um dies zu demonstrieren:

  1. Erstellen Sie eine neue Datei mit dem Namen join_timeout.py im Verzeichnis /home/labex/project.

  2. Fügen Sie den folgenden Code hinzu:

import threading
import time

def long_running_task():
    """A function that simulates a very long-running task."""
    print("Long-running task started...")
    time.sleep(10)  ## Simulate a 10-second task
    print("Long-running task completed!")

## Create and start the thread
task_thread = threading.Thread(target=long_running_task)
task_thread.start()

## Wait for the thread to complete, but only for up to 3 seconds
print("Main thread: Waiting for up to 3 seconds...")
task_thread.join(timeout=3)

## Check if the thread is still running
if task_thread.is_alive():
    print("Main thread: The task is still running, but I'm continuing anyway!")
else:
    print("Main thread: The task has completed within the timeout period.")

## Continue with the main thread
print("Main thread: Continuing with other operations...")
## Let's sleep a bit to see the long-running task complete
time.sleep(8)
print("Main thread: Finished.")
  1. Speichern Sie die Datei und führen Sie sie aus:
python3 /home/labex/project/join_timeout.py

Die Ausgabe sollte so aussehen:

Long-running task started...
Main thread: Waiting for up to 3 seconds...
Main thread: The task is still running, but I'm continuing anyway!
Main thread: Continuing with other operations...
Long-running task completed!
Main thread: Finished.

In diesem Beispiel wartet der Haupt-Thread bis zu 3 Sekunden, bis der Task-Thread abgeschlossen ist. Da die Aufgabe 10 Sekunden dauert, fährt der Haupt-Thread nach dem Timeout fort, während der Task-Thread im Hintergrund weiterläuft.

Dieser Ansatz ist nützlich, wenn Sie Threads eine Chance geben möchten, sich zu beenden, aber nach einer bestimmten Zeitspanne unabhängig davon fortfahren müssen.

Arbeiten mit mehreren Threads

In realweltlichen Anwendungen müssen Sie oft gleichzeitig mit mehreren Threads arbeiten. In diesem Schritt erfahren Sie, wie Sie mehrere Threads in Python erstellen, verwalten und synchronisieren können.

Erstellen mehrerer Threads

Bei der Bearbeitung mehrerer ähnlicher Aufgaben ist es üblich, mehrere Threads zu erstellen, um diese gleichzeitig zu verarbeiten. Dies kann die Leistung erheblich verbessern, insbesondere bei I/O-gebundenen Operationen wie dem Herunterladen von Dateien oder dem Tätigen von Netzwerkanforderungen.

Erstellen wir ein Beispiel, das mehrere Threads verwendet, um eine Liste von Aufgaben zu verarbeiten:

  1. Erstellen Sie eine neue Datei mit dem Namen multiple_threads.py im Verzeichnis /home/labex/project.

  2. Fügen Sie den folgenden Code hinzu:

import threading
import time
import random

def process_task(task_id):
    """Function to process a single task."""
    print(f"Starting task {task_id}...")
    ## Simulate variable processing time
    processing_time = random.uniform(1, 3)
    time.sleep(processing_time)
    print(f"Task {task_id} completed in {processing_time:.2f} seconds.")
    return task_id

## List of tasks to process
tasks = list(range(1, 6))  ## Tasks with IDs 1 through 5

## Create a list to store our threads
threads = []

## Create and start a thread for each task
for task_id in tasks:
    thread = threading.Thread(target=process_task, args=(task_id,))
    threads.append(thread)
    print(f"Created thread for task {task_id}")
    thread.start()

print(f"All {len(threads)} threads have been started")

## Wait for all threads to complete
for thread in threads:
    thread.join()

print("All tasks have been completed!")
  1. Speichern Sie die Datei und führen Sie sie aus:
python3 /home/labex/project/multiple_threads.py

Die Ausgabe variiert jedes Mal aufgrund der zufälligen Verarbeitungszeiten, sollte aber in etwa so aussehen:

Created thread for task 1
Starting task 1...
Created thread for task 2
Starting task 2...
Created thread for task 3
Starting task 3...
Created thread for task 4
Starting task 4...
Created thread for task 5
Starting task 5...
All 5 threads have been started
Task 1 completed in 1.23 seconds.
Task 3 completed in 1.45 seconds.
Task 2 completed in 1.97 seconds.
Task 5 completed in 1.35 seconds.
Task 4 completed in 2.12 seconds.
All tasks have been completed!

Verständnis des Ausführungsflusses

In diesem Beispiel:

  1. Wir haben eine Funktion process_task() definiert, die die Verarbeitung einer Aufgabe mit einer zufälligen Dauer simuliert.
  2. Wir haben eine Liste von Aufgaben-IDs (1 bis 5) erstellt.
  3. Für jede Aufgabe haben wir einen Thread erstellt, ihn in einer Liste gespeichert und gestartet.
  4. Nachdem wir alle Threads gestartet hatten, verwendeten wir eine zweite Schleife mit join(), um auf den Abschluss jedes Threads zu warten.
  5. Erst nachdem alle Threads abgeschlossen waren, gaben wir die endgültige Nachricht aus.

Dieses Muster ist sehr nützlich, wenn Sie eine Reihe unabhängiger Aufgaben haben, die parallel verarbeitet werden können.

Thread Pool Executors

Für eine erweiterte Thread-Verwaltung bietet das concurrent.futures-Modul von Python die Klasse ThreadPoolExecutor. Diese erstellt einen Pool von Worker-Threads, die wiederverwendet werden können, was effizienter ist als das Erstellen und Zerstören von Threads für jede Aufgabe.

Schreiben wir unser Beispiel mit einem Thread-Pool neu:

  1. Erstellen Sie eine neue Datei mit dem Namen thread_pool.py im Verzeichnis /home/labex/project.

  2. Fügen Sie den folgenden Code hinzu:

import concurrent.futures
import time
import random

def process_task(task_id):
    """Function to process a single task."""
    print(f"Starting task {task_id}...")
    ## Simulate variable processing time
    processing_time = random.uniform(1, 3)
    time.sleep(processing_time)
    print(f"Task {task_id} completed in {processing_time:.2f} seconds.")
    return f"Result of task {task_id}"

## List of tasks to process
tasks = list(range(1, 6))  ## Tasks with IDs 1 through 5

## Create a ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    ## Submit all tasks and store the Future objects
    print(f"Submitting {len(tasks)} tasks to the thread pool with 3 workers...")
    future_to_task = {executor.submit(process_task, task_id): task_id for task_id in tasks}

    ## As each task completes, get its result
    for future in concurrent.futures.as_completed(future_to_task):
        task_id = future_to_task[future]
        try:
            result = future.result()
            print(f"Got result from task {task_id}: {result}")
        except Exception as e:
            print(f"Task {task_id} generated an exception: {e}")

print("All tasks have been processed!")
  1. Speichern Sie die Datei und führen Sie sie aus:
python3 /home/labex/project/thread_pool.py

Die Ausgabe variiert aufgrund der zufälligen Verarbeitungszeiten erneut, sollte aber in etwa so aussehen:

Submitting 5 tasks to the thread pool with 3 workers...
Starting task 1...
Starting task 2...
Starting task 3...
Task 2 completed in 1.15 seconds.
Starting task 4...
Got result from task 2: Result of task 2
Task 1 completed in 1.82 seconds.
Starting task 5...
Got result from task 1: Result of task 1
Task 3 completed in 2.25 seconds.
Got result from task 3: Result of task 3
Task 4 completed in 1.45 seconds.
Got result from task 4: Result of task 4
Task 5 completed in 1.67 seconds.
Got result from task 5: Result of task 5
All tasks have been processed!

Vorteile von Thread-Pools

Der Thread-Pool-Ansatz bietet mehrere Vorteile:

  1. Ressourcenverwaltung (Resource Management): Er begrenzt die Anzahl der Threads, die gleichzeitig ausgeführt werden können, und verhindert so die Erschöpfung der Systemressourcen.
  2. Aufgabenplanung (Task Scheduling): Er übernimmt die automatische Planung von Aufgaben und startet neue Aufgaben, sobald Threads verfügbar werden.
  3. Ergebnissammlung (Result Collection): Er bietet bequeme Möglichkeiten, Ergebnisse von abgeschlossenen Aufgaben zu sammeln.
  4. Ausnahmebehandlung (Exception Handling): Er vereinfacht die Behandlung von Ausnahmen in Threads.

In unserem Beispiel haben wir max_workers=3 festgelegt, was bedeutet, dass nur 3 Threads gleichzeitig ausgeführt werden, obwohl wir 5 Aufgaben haben. Wenn Threads ihre Aufgaben erledigen, werden sie für die verbleibenden Aufgaben wiederverwendet.

Thread-Pools sind besonders nützlich, wenn Sie viel mehr Aufgaben haben, als Sie gleichzeitig Threads ausführen möchten, oder wenn Aufgaben kontinuierlich generiert werden.

Thread-Timeouts und Daemon-Threads

In diesem letzten Schritt lernen Sie zwei wichtige Konzepte der Thread-Verwaltung kennen: das Festlegen von Timeouts und die Verwendung von Daemon-Threads. Diese Techniken geben Ihnen mehr Kontrolle darüber, wie sich Threads verhalten und mit dem Hauptprogramm interagieren.

Arbeiten mit Thread-Timeouts

Wie Sie in Schritt 2 gelernt haben, akzeptiert die join()-Methode einen Timeout-Parameter. Dies ist nützlich, wenn Sie darauf warten möchten, dass ein Thread abgeschlossen wird, aber nur bis zu einem bestimmten Zeitpunkt.

Erstellen wir ein praktischeres Beispiel, in dem wir eine Funktion implementieren, die versucht, Daten mit einem Timeout abzurufen:

  1. Erstellen Sie eine neue Datei mit dem Namen thread_with_timeout.py im Verzeichnis /home/labex/project.

  2. Fügen Sie den folgenden Code hinzu:

import threading
import time
import random

def fetch_data(data_id):
    """Simulate fetching data that might take varying amounts of time."""
    print(f"Fetching data #{data_id}...")

    ## Simulate different fetch times, occasionally very long
    fetch_time = random.choices([1, 8], weights=[0.8, 0.2])[0]
    time.sleep(fetch_time)

    if fetch_time > 5:  ## Simulate a slow fetch
        print(f"Data #{data_id}: Fetch took too long!")
        return None
    else:
        print(f"Data #{data_id}: Fetch completed in {fetch_time} seconds!")
        return f"Data content for #{data_id}"

def fetch_with_timeout(data_id, timeout=3):
    """Fetch data with a timeout."""
    result = [None]  ## Using a list to store result from the thread

    def target_func():
        result[0] = fetch_data(data_id)

    ## Create and start the thread
    thread = threading.Thread(target=target_func)
    thread.start()

    ## Wait for the thread with a timeout
    thread.join(timeout=timeout)

    if thread.is_alive():
        print(f"Data #{data_id}: Fetch timed out after {timeout} seconds!")
        return "TIMEOUT"
    else:
        return result[0]

## Try to fetch several pieces of data
for i in range(1, 6):
    print(f"\nAttempting to fetch data #{i}")
    result = fetch_with_timeout(i, timeout=3)
    if result == "TIMEOUT":
        print(f"Main thread: Fetch for data #{i} timed out, moving on...")
    elif result is None:
        print(f"Main thread: Fetch for data #{i} completed but returned no data.")
    else:
        print(f"Main thread: Successfully fetched: {result}")

print("\nAll fetch attempts completed!")
  1. Speichern Sie die Datei und führen Sie sie aus:
python3 /home/labex/project/thread_with_timeout.py

Die Ausgabe variiert, sollte aber in etwa so aussehen:

Attempting to fetch data #1
Fetching data #1...
Data #1: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #1

Attempting to fetch data #2
Fetching data #2...
Data #2: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #2

Attempting to fetch data #3
Fetching data #3...
Data #3: Fetch timed out after 3 seconds!
Main thread: Fetch for data #3 timed out, moving on...
Data #3: Fetch took too long!

Attempting to fetch data #4
Fetching data #4...
Data #4: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #4

Attempting to fetch data #5
Fetching data #5...
Data #5: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #5

All fetch attempts completed!

Dieses Beispiel demonstriert:

  1. Eine Funktion, die versucht, Daten abzurufen und möglicherweise langsam ist
  2. Eine Wrapper-Funktion, die Threading mit einem Timeout verwendet
  3. Wie man Timeouts elegant behandelt und mit anderen Operationen fortfährt

Daemon-Threads verstehen

In Python sind Daemon-Threads Threads, die im Hintergrund ausgeführt werden. Der Hauptunterschied zwischen Daemon- und Nicht-Daemon-Threads besteht darin, dass Python nicht auf den Abschluss von Daemon-Threads wartet, bevor es beendet wird. Dies ist nützlich für Threads, die Hintergrundaufgaben ausführen, die nicht verhindern sollten, dass das Programm beendet wird.

Erstellen wir ein Beispiel, um Daemon-Threads zu demonstrieren:

  1. Erstellen Sie eine neue Datei mit dem Namen daemon_threads.py im Verzeichnis /home/labex/project.

  2. Fügen Sie den folgenden Code hinzu:

import threading
import time

def background_task(name, interval):
    """A task that runs in the background at regular intervals."""
    count = 0
    while True:
        count += 1
        print(f"{name}: Iteration {count} at {time.strftime('%H:%M:%S')}")
        time.sleep(interval)

def main_task():
    """The main task that runs for a set amount of time."""
    print("Main task: Starting...")
    time.sleep(5)
    print("Main task: Completed!")

## Create two background threads
print("Creating background monitoring threads...")
monitor1 = threading.Thread(target=background_task, args=("Monitor-1", 1), daemon=True)
monitor2 = threading.Thread(target=background_task, args=("Monitor-2", 2), daemon=True)

## Start the background threads
monitor1.start()
monitor2.start()

print("Background monitors started, now starting main task...")

## Run the main task
main_task()

print("Main task completed, program will exit without waiting for daemon threads.")
print("Daemon threads will be terminated when the program exits.")
  1. Speichern Sie die Datei und führen Sie sie aus:
python3 /home/labex/project/daemon_threads.py

Die Ausgabe sollte in etwa so aussehen:

Creating background monitoring threads...
Background monitors started, now starting main task...
Main task: Starting...
Monitor-1: Iteration 1 at 14:25:10
Monitor-2: Iteration 1 at 14:25:10
Monitor-1: Iteration 2 at 14:25:11
Monitor-1: Iteration 3 at 14:25:12
Monitor-2: Iteration 2 at 14:25:12
Monitor-1: Iteration 4 at 14:25:13
Monitor-1: Iteration 5 at 14:25:14
Monitor-2: Iteration 3 at 14:25:14
Main task: Completed!
Main task completed, program will exit without waiting for daemon threads.
Daemon threads will be terminated when the program exits.

In diesem Beispiel:

  1. Wir haben zwei Daemon-Threads erstellt, die kontinuierlich laufen und in regelmäßigen Abständen Nachrichten ausgeben.
  2. Wir haben daemon=True beim Erstellen der Threads gesetzt, wodurch sie als Daemon-Threads gekennzeichnet werden.
  3. Der Haupt-Thread läuft 5 Sekunden lang und wird dann beendet.
  4. Wenn der Haupt-Thread beendet wird, beendet sich das Programm, und die Daemon-Threads werden ebenfalls automatisch beendet.

Nicht-Daemon- vs. Daemon-Threads

Um den Unterschied besser zu verstehen, erstellen wir ein weiteres Beispiel, das Daemon- und Nicht-Daemon-Threads vergleicht:

  1. Erstellen Sie eine neue Datei mit dem Namen daemon_comparison.py im Verzeichnis /home/labex/project.

  2. Fügen Sie den folgenden Code hinzu:

import threading
import time

def task(name, seconds, daemon=False):
    """A task that runs for a specified amount of time."""
    print(f"{name} starting {'(daemon)' if daemon else '(non-daemon)'}")
    time.sleep(seconds)
    print(f"{name} finished after {seconds} seconds")

## Create a non-daemon thread that runs for 8 seconds
non_daemon_thread = threading.Thread(
    target=task,
    args=("Non-daemon thread", 8, False),
    daemon=False  ## This is the default, so it's not actually needed
)

## Create a daemon thread that runs for 8 seconds
daemon_thread = threading.Thread(
    target=task,
    args=("Daemon thread", 8, True),
    daemon=True
)

## Start both threads
non_daemon_thread.start()
daemon_thread.start()

## Let the main thread run for 3 seconds
print("Main thread will run for 3 seconds...")
time.sleep(3)

## Check which threads are still running
print("\nAfter 3 seconds:")
print(f"Daemon thread is alive: {daemon_thread.is_alive()}")
print(f"Non-daemon thread is alive: {non_daemon_thread.is_alive()}")

print("\nMain thread is finishing. Here's what will happen:")
print("1. The program will wait for all non-daemon threads to complete")
print("2. Daemon threads will be terminated when the program exits")

print("\nWaiting for non-daemon threads to finish...")
## We don't need to join the non-daemon thread, Python will wait for it
## But we'll explicitly join it for clarity
non_daemon_thread.join()
print("All non-daemon threads have finished, program will exit now.")
  1. Speichern Sie die Datei und führen Sie sie aus:
python3 /home/labex/project/daemon_comparison.py

Die Ausgabe sollte so aussehen:

Non-daemon thread starting (non-daemon)
Daemon thread starting (daemon)
Main thread will run for 3 seconds...

After 3 seconds:
Daemon thread is alive: True
Non-daemon thread is alive: True

Main thread is finishing. Here's what will happen:
1. The program will wait for all non-daemon threads to complete
2. Daemon threads will be terminated when the program exits

Waiting for non-daemon threads to finish...
Non-daemon thread finished after 8 seconds
All non-daemon threads have finished, program will exit now.

Wichtige Beobachtungen:

  1. Beide Threads starten und laufen gleichzeitig.
  2. Nach 3 Sekunden laufen beide Threads noch.
  3. Das Programm wartet, bis der Nicht-Daemon-Thread fertig ist (nach 8 Sekunden).
  4. Der Daemon-Thread läuft noch, wenn das Programm beendet wird, wird aber beendet.
  5. Der Daemon-Thread kommt nie dazu, seine Abschlussmeldung auszugeben, da er beendet wird, wenn das Programm beendet wird.

Wann Daemon-Threads verwendet werden sollten

Daemon-Threads sind nützlich für:

  • Hintergrundüberwachungsaufgaben
  • Aufräumarbeiten
  • Dienste, die für die Dauer des Programms ausgeführt werden sollen, aber nicht verhindern, dass es beendet wird
  • Timer-Threads, die Ereignisse in regelmäßigen Abständen auslösen

Nicht-Daemon-Threads sind geeignet für:

  • Kritische Operationen, die abgeschlossen werden müssen
  • Aufgaben, die nicht unterbrochen werden sollten
  • Operationen, die sauber abgeschlossen werden müssen, bevor das Programm beendet wird

Zu verstehen, wann welcher Typ verwendet werden soll, ist ein wichtiger Bestandteil der Entwicklung robuster Multi-Thread-Anwendungen.

Zusammenfassung

In diesem Lab haben Sie die wesentlichen Techniken für die Arbeit mit Python-Threads und deren Wartezeit bis zum Abschluss gelernt. Hier ist eine Zusammenfassung der wichtigsten behandelten Konzepte:

  1. Erstellen und Starten von Threads: Sie haben gelernt, wie man ein Thread-Objekt erstellt, die Zielfunktion angibt und seine Ausführung mit der start()-Methode startet.

  2. Warten auf Threads mit join(): Sie haben entdeckt, wie Sie die join()-Methode verwenden, um auf den Abschluss eines Threads zu warten, bevor Sie mit dem Hauptprogramm fortfahren, um eine ordnungsgemäße Synchronisierung sicherzustellen.

  3. Arbeiten mit mehreren Threads: Sie haben das Erstellen und Verwalten mehrerer Threads geübt, sowohl manuell als auch unter Verwendung der Klasse ThreadPoolExecutor für eine effizientere Thread-Verwaltung.

  4. Thread-Timeouts und Daemon-Threads: Sie haben fortgeschrittene Themen wie das Festlegen von Timeouts für Thread-Operationen und die Verwendung von Daemon-Threads für Hintergrundaufgaben untersucht.

Diese Fähigkeiten bilden die Grundlage für die Entwicklung von Multi-Thread-Anwendungen in Python. Multi-Threading ermöglicht es Ihren Programmen, mehrere Aufgaben gleichzeitig auszuführen, wodurch die Leistung und Reaktionsfähigkeit verbessert wird, insbesondere bei I/O-gebundenen Operationen.

Denken Sie bei der weiteren Arbeit mit Threads an diese Best Practices:

  • Verwenden Sie Threads für I/O-gebundene Aufgaben, nicht für CPU-gebundene Aufgaben (erwägen Sie die Verwendung von Multiprocessing für letztere)
  • Achten Sie auf gemeinsam genutzte Ressourcen und verwenden Sie geeignete Synchronisierungsmechanismen
  • Erwägen Sie die Verwendung von Abstraktionen auf höherer Ebene wie ThreadPoolExecutor für die Verwaltung mehrerer Threads
  • Verwenden Sie Daemon-Threads für Hintergrundaufgaben, die nicht verhindern sollten, dass das Programm beendet wird

Mit diesen Fähigkeiten und Praktiken sind Sie nun in der Lage, effizientere und reaktionsfähigere Python-Anwendungen mit Multi-Threading-Techniken zu erstellen.