Timeouts de Thread e Daemon Threads
Nesta etapa final, você aprenderá sobre dois conceitos importantes no gerenciamento de threads: definir timeouts e usar daemon threads. Essas técnicas oferecem mais controle sobre como as threads se comportam e interagem com o programa principal.
Trabalhando com Timeouts de Thread
Como você aprendeu na Etapa 2, o método join() aceita um parâmetro de timeout. Isso é útil quando você deseja esperar que uma thread seja concluída, mas apenas até um determinado ponto.
Vamos criar um exemplo mais prático onde implementamos uma função que tenta buscar dados com um timeout:
-
Crie um novo arquivo chamado thread_with_timeout.py no diretório /home/labex/project.
-
Adicione o seguinte código:
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!")
- Salve o arquivo e execute-o:
python3 /home/labex/project/thread_with_timeout.py
A saída variará, mas deve ser semelhante a esta:
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!
Este exemplo demonstra:
- Uma função que tenta buscar dados e pode ser lenta
- Uma função wrapper que usa threading com um timeout
- Como lidar com timeouts de forma elegante e continuar com outras operações
Entendendo Daemon Threads
Em Python, daemon threads são threads que são executadas em segundo plano. A principal diferença entre daemon threads e threads não-daemon é que o Python não esperará que as daemon threads sejam concluídas antes de sair. Isso é útil para threads que executam tarefas em segundo plano que não devem impedir a saída do programa.
Vamos criar um exemplo para demonstrar daemon threads:
-
Crie um novo arquivo chamado daemon_threads.py no diretório /home/labex/project.
-
Adicione o seguinte código:
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.")
- Salve o arquivo e execute-o:
python3 /home/labex/project/daemon_threads.py
A saída deve ser semelhante a esta:
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.
Neste exemplo:
- Criamos duas daemon threads que são executadas continuamente, imprimindo mensagens em intervalos regulares.
- Definimos
daemon=True ao criar as threads, o que as marca como daemon threads.
- A thread principal é executada por 5 segundos e depois sai.
- Quando a thread principal sai, o programa termina e as daemon threads também são automaticamente terminadas.
Threads Não-Daemon vs. Daemon
Para entender melhor a diferença, vamos criar mais um exemplo que compara daemon e non-daemon threads:
-
Crie um novo arquivo chamado daemon_comparison.py no diretório /home/labex/project.
-
Adicione o seguinte código:
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.")
- Salve o arquivo e execute-o:
python3 /home/labex/project/daemon_comparison.py
A saída deve ser semelhante a esta:
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.
Observações chave:
- Ambas as threads iniciam e são executadas simultaneamente.
- Após 3 segundos, ambas as threads ainda estão em execução.
- O programa espera que a thread não-daemon termine (após 8 segundos).
- A thread daemon ainda está em execução quando o programa sai, mas é terminada.
- A thread daemon nunca chega a imprimir sua mensagem de conclusão porque é terminada quando o programa sai.
Quando Usar Daemon Threads
Daemon threads são úteis para:
- Tarefas de monitoramento em segundo plano
- Operações de limpeza
- Serviços que devem ser executados durante a duração do programa, mas não impedi-lo de sair
- Threads de temporizador que acionam eventos em intervalos regulares
Threads não-daemon são apropriadas para:
- Operações críticas que devem ser concluídas
- Tarefas que não devem ser interrompidas
- Operações que devem ser finalizadas de forma limpa antes que o programa saia
Entender quando usar cada tipo é uma parte importante do projeto de aplicações multi-threaded robustas.