Délais d'attente des threads et threads démon
Dans cette dernière étape, vous apprendrez deux concepts importants de la gestion des threads : la définition de délais d'attente et l'utilisation de threads démon. Ces techniques vous donnent plus de contrôle sur le comportement des threads et leur interaction avec le programme principal.
Travailler avec les délais d'attente des threads
Comme vous l'avez appris à l'étape 2, la méthode join() accepte un paramètre de délai d'attente (timeout). Ceci est utile lorsque vous souhaitez attendre qu'un thread se termine, mais uniquement jusqu'à un certain point.
Créons un exemple plus pratique où nous implémentons une fonction qui tente de récupérer des données avec un délai d'attente :
-
Créez un nouveau fichier nommé thread_with_timeout.py dans le répertoire /home/labex/project.
-
Ajoutez le code suivant :
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!")
- Enregistrez le fichier et exécutez-le :
python3 /home/labex/project/thread_with_timeout.py
La sortie variera, mais devrait ressembler à ceci :
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!
Cet exemple démontre :
- Une fonction qui tente de récupérer des données et qui peut être lente
- Une fonction wrapper qui utilise le threading avec un délai d'attente
- Comment gérer les délais d'attente avec élégance et continuer avec d'autres opérations
Comprendre les threads démon
En Python, les threads démon sont des threads qui s'exécutent en arrière-plan. La principale différence entre les threads démon et les threads non-démon est que Python n'attendra pas que les threads démon se terminent avant de quitter. Ceci est utile pour les threads qui effectuent des tâches en arrière-plan qui ne doivent pas empêcher le programme de se terminer.
Créons un exemple pour démontrer les threads démon :
-
Créez un nouveau fichier nommé daemon_threads.py dans le répertoire /home/labex/project.
-
Ajoutez le code suivant :
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.")
- Enregistrez le fichier et exécutez-le :
python3 /home/labex/project/daemon_threads.py
La sortie devrait ressembler à ceci :
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.
Dans cet exemple :
- Nous avons créé deux threads démon qui s'exécutent en continu, imprimant des messages à intervalles réguliers.
- Nous avons défini
daemon=True lors de la création des threads, ce qui les marque comme des threads démon.
- Le thread principal s'exécute pendant 5 secondes, puis se termine.
- Lorsque le thread principal se termine, le programme se termine et les threads démon sont également automatiquement terminés.
Threads non-démon vs. Threads démon
Pour mieux comprendre la différence, créons un autre exemple qui compare les threads démon et non-démon :
-
Créez un nouveau fichier nommé daemon_comparison.py dans le répertoire /home/labex/project.
-
Ajoutez le code suivant :
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.")
- Enregistrez le fichier et exécutez-le :
python3 /home/labex/project/daemon_comparison.py
La sortie devrait ressembler à ceci :
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.
Observations clés :
- Les deux threads démarrent et s'exécutent simultanément.
- Après 3 secondes, les deux threads sont toujours en cours d'exécution.
- Le programme attend la fin du thread non-démon (après 8 secondes).
- Le thread démon est toujours en cours d'exécution lorsque le programme se termine, mais il est arrêté.
- Le thread démon n'imprime jamais son message de fin car il est arrêté lorsque le programme se termine.
Quand utiliser les threads démon
Les threads démon sont utiles pour :
- Les tâches de surveillance en arrière-plan
- Les opérations de nettoyage
- Les services qui doivent s'exécuter pendant la durée du programme, mais sans l'empêcher de se terminer
- Les threads de minuterie qui déclenchent des événements à intervalles réguliers
Les threads non-démon sont appropriés pour :
- Les opérations critiques qui doivent être terminées
- Les tâches qui ne doivent pas être interrompues
- Les opérations qui doivent se terminer proprement avant la fin du programme
Comprendre quand utiliser chaque type est une partie importante de la conception d'applications multi-threadées robustes.