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:
-
Erstellen Sie eine neue Datei mit dem Namen thread_with_timeout.py im Verzeichnis /home/labex/project.
-
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!")
- 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:
- Eine Funktion, die versucht, Daten abzurufen und möglicherweise langsam ist
- Eine Wrapper-Funktion, die Threading mit einem Timeout verwendet
- 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:
-
Erstellen Sie eine neue Datei mit dem Namen daemon_threads.py im Verzeichnis /home/labex/project.
-
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.")
- 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:
- Wir haben zwei Daemon-Threads erstellt, die kontinuierlich laufen und in regelmäßigen Abständen Nachrichten ausgeben.
- Wir haben
daemon=True beim Erstellen der Threads gesetzt, wodurch sie als Daemon-Threads gekennzeichnet werden.
- Der Haupt-Thread läuft 5 Sekunden lang und wird dann beendet.
- 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:
-
Erstellen Sie eine neue Datei mit dem Namen daemon_comparison.py im Verzeichnis /home/labex/project.
-
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.")
- 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:
- Beide Threads starten und laufen gleichzeitig.
- Nach 3 Sekunden laufen beide Threads noch.
- Das Programm wartet, bis der Nicht-Daemon-Thread fertig ist (nach 8 Sekunden).
- Der Daemon-Thread läuft noch, wenn das Programm beendet wird, wird aber beendet.
- 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.