Introducción
Dominar cómo esperar a que los hilos (threads) de Python finalicen es esencial para construir aplicaciones robustas y confiables. En programas multi-hilo, la sincronización adecuada asegura que las operaciones se completen en el orden correcto y que los recursos se utilicen eficientemente.
En este laboratorio, aprenderá a crear hilos de Python, a esperar a que se completen y a manejar múltiples hilos. Estas habilidades son fundamentales para desarrollar aplicaciones concurrentes que pueden realizar múltiples tareas simultáneamente, manteniendo una sincronización adecuada.
Creando tu Primer Hilo de Python
El módulo threading de Python proporciona una forma sencilla de crear y gestionar hilos (threads). En este paso, aprenderá a crear un hilo básico y a observar su comportamiento.
Entendiendo los Hilos (Threads)
Un hilo (thread) es un flujo de ejecución separado en un programa. Cuando ejecuta un script de Python, comienza con un único hilo llamado el hilo principal (main thread). Al crear hilos adicionales, su programa puede realizar múltiples tareas concurrentemente.
Los hilos son útiles para:
- Ejecutar operaciones que consumen mucho tiempo sin bloquear el programa principal
- Procesar tareas en paralelo para mejorar el rendimiento
- Manejar múltiples conexiones de cliente en una aplicación de servidor
Creando un Hilo Simple
Comencemos creando un script de Python simple que demuestre cómo crear e iniciar un hilo.
Abra un nuevo archivo en el editor haciendo clic en el menú "Archivo", seleccionando "Nuevo archivo" y luego guardándolo como
simple_thread.pyen el directorio/home/labex/project.Agregue el siguiente código al archivo:
import threading
import time
def print_numbers():
"""Función que imprime números del 1 al 5 con un retraso."""
for i in range(1, 6):
print(f"Número {i} del hilo")
time.sleep(1) ## Espera 1 segundo
## Crea un hilo que apunta a la función print_numbers
number_thread = threading.Thread(target=print_numbers)
## Inicia el hilo
print("Iniciando el hilo...")
number_thread.start()
## El hilo principal continúa la ejecución
print("El hilo principal continúa ejecutándose...")
print("El hilo principal está haciendo otro trabajo...")
## Espera 2 segundos para demostrar que ambos hilos se ejecutan concurrentemente
time.sleep(2)
print("¡El hilo principal terminó su trabajo!")
Guarde el archivo presionando
Ctrl+So haciendo clic en "Archivo" > "Guardar".Ejecute el script abriendo una terminal (si aún no está abierta) y ejecutando:
python3 /home/labex/project/simple_thread.py
Debería ver una salida similar a esta:
Iniciando el hilo...
El hilo principal continúa ejecutándose...
El hilo principal está haciendo otro trabajo...
Número 1 del hilo
Número 2 del hilo
¡El hilo principal terminó su trabajo!
Número 3 del hilo
Número 4 del hilo
Número 5 del hilo
Analizando lo que Sucedió
En este ejemplo:
- Importamos los módulos
threadingytime. - Definimos una función
print_numbers()que imprime números del 1 al 5 con un retraso de 1 segundo entre cada uno. - Creamos un objeto de hilo, especificando la función a ejecutar usando el parámetro
target. - Iniciamos el hilo usando el método
start(). - El hilo principal continuó su ejecución, imprimiendo mensajes y esperando 2 segundos.
- Tanto el hilo principal como nuestro hilo de números se ejecutaron concurrentemente, por lo que la salida está intercalada.
Observe que el hilo principal finalizó antes de que el hilo de números imprimiera todos sus números. Esto se debe a que los hilos se ejecutan independientemente y, de forma predeterminada, el programa de Python saldrá cuando el hilo principal finalice, incluso si otros hilos aún se están ejecutando.
En el siguiente paso, aprenderá a esperar a que un hilo se complete usando el método join().
Esperando a que un Hilo se Complete con join()
En el paso anterior, creó un hilo que se ejecutaba independientemente del hilo principal. Sin embargo, hay muchas situaciones en las que necesita esperar a que un hilo termine su trabajo antes de continuar con el resto de su programa. Aquí es donde el método join() resulta útil.
Entendiendo el Método join()
El método join() de un objeto de hilo bloquea el hilo que lo llama (generalmente el hilo principal) hasta que el hilo cuyo método join() se llama finaliza. Esto es esencial cuando:
- El hilo principal necesita resultados de un hilo de trabajo
- Necesita asegurarse de que todos los hilos se completen antes de salir del programa
- El orden de las operaciones es importante para la lógica de su aplicación
Creando un Hilo y Esperando a que se Complete
Modifiquemos nuestro ejemplo anterior para demostrar cómo esperar a que un hilo se complete usando el método join().
Cree un nuevo archivo llamado
join_thread.pyen el directorio/home/labex/project.Agregue el siguiente código al archivo:
import threading
import time
def calculate_sum(numbers):
"""Función que calcula la suma de números con un retraso."""
print("Iniciando el cálculo...")
time.sleep(3) ## Simula un cálculo que consume tiempo
result = sum(numbers)
print(f"Resultado del cálculo: {result}")
return result
## Crea una lista de números
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
## Crea un hilo que apunta a la función calculate_sum
calculation_thread = threading.Thread(target=calculate_sum, args=(numbers,))
## Inicia el hilo
print("Hilo principal: Iniciando el hilo de cálculo...")
calculation_thread.start()
## Realiza algún otro trabajo en el hilo principal
print("Hilo principal: Haciendo otro trabajo mientras espera...")
for i in range(5):
print(f"Hilo principal: Trabajando... ({i+1}/5)")
time.sleep(0.5)
## Espera a que el hilo de cálculo se complete
print("Hilo principal: Esperando a que el hilo de cálculo finalice...")
calculation_thread.join()
print("Hilo principal: ¡El hilo de cálculo ha finalizado!")
## Continúa con el hilo principal
print("Hilo principal: Continuando con el resto del programa...")
- Guarde el archivo y ejecútelo con el siguiente comando:
python3 /home/labex/project/join_thread.py
Debería ver una salida similar a esta:
Hilo principal: Iniciando el hilo de cálculo...
Iniciando el cálculo...
Hilo principal: Haciendo otro trabajo mientras espera...
Hilo principal: Trabajando... (1/5)
Hilo principal: Trabajando... (2/5)
Hilo principal: Trabajando... (3/5)
Hilo principal: Trabajando... (4/5)
Hilo principal: Trabajando... (5/5)
Hilo principal: Esperando a que el hilo de cálculo finalice...
Resultado del cálculo: 55
Hilo principal: ¡El hilo de cálculo ha finalizado!
Hilo principal: Continuando con el resto del programa...
La Importancia de join()
En este ejemplo:
- Creamos un hilo que realiza un cálculo (sumando números).
- El hilo principal hizo algún otro trabajo concurrentemente.
- Cuando el hilo principal necesitaba asegurarse de que el cálculo se completara, llamó a
calculation_thread.join(). - El método
join()hizo que el hilo principal esperara hasta que el hilo de cálculo finalizara. - Después de que el hilo de cálculo se completó, el hilo principal continuó su ejecución.
Este patrón es muy útil cuando necesita asegurarse de que todas las tareas en hilos se completen antes de continuar con el resto de su programa. Sin join(), el hilo principal podría continuar e incluso salir antes de que los hilos de trabajo hayan completado sus tareas.
Usando join() con un Timeout
A veces, es posible que desee esperar a un hilo, pero no indefinidamente. El método join() acepta un parámetro de tiempo de espera (timeout) opcional que especifica el número máximo de segundos a esperar.
Modifiquemos nuestro código para demostrar esto:
Cree un nuevo archivo llamado
join_timeout.pyen el directorio/home/labex/project.Agregue el siguiente código:
import threading
import time
def long_running_task():
"""Una función que simula una tarea de muy larga duración."""
print("Tarea de larga duración iniciada...")
time.sleep(10) ## Simula una tarea de 10 segundos
print("Tarea de larga duración completada!")
## Crea e inicia el hilo
task_thread = threading.Thread(target=long_running_task)
task_thread.start()
## Espera a que el hilo se complete, pero solo hasta 3 segundos
print("Hilo principal: Esperando hasta 3 segundos...")
task_thread.join(timeout=3)
## Comprueba si el hilo todavía se está ejecutando
if task_thread.is_alive():
print("Hilo principal: ¡La tarea todavía se está ejecutando, pero de todas formas continúo!")
else:
print("Hilo principal: La tarea se ha completado dentro del período de tiempo de espera.")
## Continúa con el hilo principal
print("Hilo principal: Continuando con otras operaciones...")
## Esperemos un poco para ver la tarea de larga duración completarse
time.sleep(8)
print("Hilo principal: Finalizado.")
- Guarde el archivo y ejecútelo:
python3 /home/labex/project/join_timeout.py
La salida debería verse así:
Tarea de larga duración iniciada...
Hilo principal: Esperando hasta 3 segundos...
Hilo principal: ¡La tarea todavía se está ejecutando, pero de todas formas continúo!
Hilo principal: Continuando con otras operaciones...
Tarea de larga duración completada!
Hilo principal: Finalizado.
En este ejemplo, el hilo principal espera hasta 3 segundos a que el hilo de tarea se complete. Dado que la tarea tarda 10 segundos, el hilo principal continúa después del tiempo de espera, mientras que el hilo de tarea sigue ejecutándose en segundo plano.
Este enfoque es útil cuando desea dar a los hilos la oportunidad de completarse, pero necesita continuar independientemente después de una cierta cantidad de tiempo.
Trabajando con Múltiples Hilos
En aplicaciones del mundo real, a menudo necesita trabajar con múltiples hilos simultáneamente. Este paso le enseñará cómo crear, gestionar y sincronizar múltiples hilos en Python.
Creando Múltiples Hilos
Al tratar con múltiples tareas similares, es común crear múltiples hilos para procesarlas concurrentemente. Esto puede mejorar significativamente el rendimiento, especialmente para operaciones vinculadas a E/S (I/O-bound operations) como la descarga de archivos o la realización de solicitudes de red.
Creemos un ejemplo que utiliza múltiples hilos para procesar una lista de tareas:
Cree un nuevo archivo llamado
multiple_threads.pyen el directorio/home/labex/project.Agregue el siguiente código:
import threading
import time
import random
def process_task(task_id):
"""Función para procesar una sola tarea."""
print(f"Iniciando tarea {task_id}...")
## Simula tiempo de procesamiento variable
processing_time = random.uniform(1, 3)
time.sleep(processing_time)
print(f"Tarea {task_id} completada en {processing_time:.2f} segundos.")
return task_id
## Lista de tareas a procesar
tasks = list(range(1, 6)) ## Tareas con IDs del 1 al 5
## Crea una lista para almacenar nuestros hilos
threads = []
## Crea e inicia un hilo para cada tarea
for task_id in tasks:
thread = threading.Thread(target=process_task, args=(task_id,))
threads.append(thread)
print(f"Hilo creado para la tarea {task_id}")
thread.start()
print(f"Todos los {len(threads)} hilos han sido iniciados")
## Espera a que todos los hilos se completen
for thread in threads:
thread.join()
print("¡Todas las tareas se han completado!")
- Guarde el archivo y ejecútelo:
python3 /home/labex/project/multiple_threads.py
La salida variará cada vez debido a los tiempos de procesamiento aleatorios, pero debería ser similar a esto:
Hilo creado para la tarea 1
Iniciando tarea 1...
Hilo creado para la tarea 2
Iniciando tarea 2...
Hilo creado para la tarea 3
Iniciando tarea 3...
Hilo creado para la tarea 4
Iniciando tarea 4...
Hilo creado para la tarea 5
Iniciando tarea 5...
Todos los 5 hilos han sido iniciados
Tarea 1 completada en 1.23 segundos.
Tarea 3 completada en 1.45 segundos.
Tarea 2 completada en 1.97 segundos.
Tarea 5 completada en 1.35 segundos.
Tarea 4 completada en 2.12 segundos.
¡Todas las tareas se han completado!
Entendiendo el Flujo de Ejecución
En este ejemplo:
- Definimos una función
process_task()que simula el procesamiento de una tarea con una duración aleatoria. - Creamos una lista de IDs de tareas (1 a 5).
- Para cada tarea, creamos un hilo, lo almacenamos en una lista y lo iniciamos.
- Después de iniciar todos los hilos, usamos un segundo bucle con
join()para esperar a que cada hilo se complete. - Solo después de que todos los hilos se completaron, imprimimos el mensaje final.
Este patrón es muy útil cuando tiene un lote de tareas independientes que se pueden procesar en paralelo.
Ejecutores de Grupo de Hilos (Thread Pool Executors)
Para una gestión de hilos más avanzada, el módulo concurrent.futures de Python proporciona la clase ThreadPoolExecutor. Esto crea un grupo de hilos de trabajo que se pueden reutilizar, lo que es más eficiente que crear y destruir hilos para cada tarea.
Reescribamos nuestro ejemplo usando un grupo de hilos:
Cree un nuevo archivo llamado
thread_pool.pyen el directorio/home/labex/project.Agregue el siguiente código:
import concurrent.futures
import time
import random
def process_task(task_id):
"""Función para procesar una sola tarea."""
print(f"Iniciando tarea {task_id}...")
## Simula tiempo de procesamiento variable
processing_time = random.uniform(1, 3)
time.sleep(processing_time)
print(f"Tarea {task_id} completada en {processing_time:.2f} segundos.")
return f"Resultado de la tarea {task_id}"
## Lista de tareas a procesar
tasks = list(range(1, 6)) ## Tareas con IDs del 1 al 5
## Crea un ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
## Envía todas las tareas y almacena los objetos Future
print(f"Enviando {len(tasks)} tareas al grupo de hilos con 3 trabajadores...")
future_to_task = {executor.submit(process_task, task_id): task_id for task_id in tasks}
## A medida que cada tarea se completa, obtiene su resultado
for future in concurrent.futures.as_completed(future_to_task):
task_id = future_to_task[future]
try:
result = future.result()
print(f"Obtenido resultado de la tarea {task_id}: {result}")
except Exception as e:
print(f"La tarea {task_id} generó una excepción: {e}")
print("¡Todas las tareas han sido procesadas!")
- Guarde el archivo y ejecútelo:
python3 /home/labex/project/thread_pool.py
La salida variará nuevamente debido a los tiempos de procesamiento aleatorios, pero debería ser similar a esto:
Enviando 5 tareas al grupo de hilos con 3 trabajadores...
Iniciando tarea 1...
Iniciando tarea 2...
Iniciando tarea 3...
Tarea 2 completada en 1.15 segundos.
Iniciando tarea 4...
Obtenido resultado de la tarea 2: Resultado de la tarea 2
Tarea 1 completada en 1.82 segundos.
Iniciando tarea 5...
Obtenido resultado de la tarea 1: Resultado de la tarea 1
Tarea 3 completada en 2.25 segundos.
Obtenido resultado de la tarea 3: Resultado de la tarea 3
Tarea 4 completada en 1.45 segundos.
Obtenido resultado de la tarea 4: Resultado de la tarea 4
Tarea 5 completada en 1.67 segundos.
Obtenido resultado de la tarea 5: Resultado de la tarea 5
¡Todas las tareas han sido procesadas!
Beneficios de los Grupos de Hilos
El enfoque del grupo de hilos ofrece varias ventajas:
- Gestión de Recursos: Limita el número de hilos que pueden ejecutarse simultáneamente, evitando el agotamiento de los recursos del sistema.
- Programación de Tareas: Maneja la programación de tareas automáticamente, iniciando nuevas tareas a medida que los hilos están disponibles.
- Recopilación de Resultados: Proporciona formas convenientes de recopilar resultados de tareas completadas.
- Manejo de Excepciones: Hace que el manejo de excepciones en los hilos sea más sencillo.
En nuestro ejemplo, establecimos max_workers=3, lo que significa que solo 3 hilos se ejecutarán a la vez, aunque tengamos 5 tareas. A medida que los hilos completan sus tareas, se reutilizan para las tareas restantes.
Los grupos de hilos son particularmente útiles cuando tiene muchas más tareas de las que desea que los hilos se ejecuten simultáneamente, o cuando las tareas se generan continuamente.
Tiempos de Espera de Hilos y Hilos Demonios
En este paso final, aprenderá sobre dos conceptos importantes en la gestión de hilos: establecer tiempos de espera y usar hilos demonios. Estas técnicas le dan más control sobre cómo se comportan los hilos e interactúan con el programa principal.
Trabajando con Tiempos de Espera de Hilos
Como aprendió en el Paso 2, el método join() acepta un parámetro de tiempo de espera (timeout). Esto es útil cuando desea esperar a que un hilo se complete, pero solo hasta un cierto punto.
Creemos un ejemplo más práctico donde implementamos una función que intenta obtener datos con un tiempo de espera:
Cree un nuevo archivo llamado
thread_with_timeout.pyen el directorio/home/labex/project.Agregue el siguiente código:
import threading
import time
import random
def fetch_data(data_id):
"""Simula la obtención de datos que podrían tardar cantidades variables de tiempo."""
print(f"Obteniendo datos #{data_id}...")
## Simula diferentes tiempos de obtención, ocasionalmente muy largos
fetch_time = random.choices([1, 8], weights=[0.8, 0.2])[0]
time.sleep(fetch_time)
if fetch_time > 5: ## Simula una obtención lenta
print(f"Datos #{data_id}: ¡La obtención tardó demasiado!")
return None
else:
print(f"Datos #{data_id}: Obtención completada en {fetch_time} segundos!")
return f"Contenido de datos para #{data_id}"
def fetch_with_timeout(data_id, timeout=3):
"""Obtiene datos con un tiempo de espera."""
result = [None] ## Usando una lista para almacenar el resultado del hilo
def target_func():
result[0] = fetch_data(data_id)
## Crea e inicia el hilo
thread = threading.Thread(target=target_func)
thread.start()
## Espera al hilo con un tiempo de espera
thread.join(timeout=timeout)
if thread.is_alive():
print(f"Datos #{data_id}: ¡La obtención expiró después de {timeout} segundos!")
return "TIMEOUT"
else:
return result[0]
## Intenta obtener varios datos
for i in range(1, 6):
print(f"\nIntentando obtener datos #{i}")
result = fetch_with_timeout(i, timeout=3)
if result == "TIMEOUT":
print(f"Hilo principal: La obtención de datos #{i} expiró, continuando...")
elif result is None:
print(f"Hilo principal: La obtención de datos #{i} se completó pero no devolvió datos.")
else:
print(f"Hilo principal: Obtenido con éxito: {result}")
print("\n¡Todos los intentos de obtención se completaron!")
- Guarde el archivo y ejecútelo:
python3 /home/labex/project/thread_with_timeout.py
La salida variará, pero debería ser similar a esto:
Intentando obtener datos #1
Obteniendo datos #1...
Datos #1: Obtención completada en 1 segundos!
Hilo principal: Obtenido con éxito: Contenido de datos para #1
Intentando obtener datos #2
Obteniendo datos #2...
Datos #2: Obtención completada en 1 segundos!
Hilo principal: Obtenido con éxito: Contenido de datos para #2
Intentando obtener datos #3
Obteniendo datos #3...
Datos #3: La obtención expiró después de 3 segundos!
Hilo principal: La obtención de datos #3 expiró, continuando...
Datos #3: ¡La obtención tardó demasiado!
Intentando obtener datos #4
Obteniendo datos #4...
Datos #4: Obtención completada en 1 segundos!
Hilo principal: Obtenido con éxito: Contenido de datos para #4
Intentando obtener datos #5
Obteniendo datos #5...
Datos #5: Obtención completada en 1 segundos!
Hilo principal: Obtenido con éxito: Contenido de datos para #5
¡Todos los intentos de obtención se completaron!
Este ejemplo demuestra:
- Una función que intenta obtener datos y podría ser lenta
- Una función envolvente que usa hilos con un tiempo de espera
- Cómo manejar los tiempos de espera con elegancia y continuar con otras operaciones
Entendiendo los Hilos Demonios
En Python, los hilos demonios son hilos que se ejecutan en segundo plano. La diferencia clave entre los hilos demonios y los no demonios es que Python no esperará a que los hilos demonios se completen antes de salir. Esto es útil para los hilos que realizan tareas en segundo plano que no deberían impedir que el programa salga.
Creemos un ejemplo para demostrar los hilos demonios:
Cree un nuevo archivo llamado
daemon_threads.pyen el directorio/home/labex/project.Agregue el siguiente código:
import threading
import time
def background_task(name, interval):
"""Una tarea que se ejecuta en segundo plano a intervalos regulares."""
count = 0
while True:
count += 1
print(f"{name}: Iteración {count} a las {time.strftime('%H:%M:%S')}")
time.sleep(interval)
def main_task():
"""La tarea principal que se ejecuta durante un período de tiempo establecido."""
print("Tarea principal: Iniciando...")
time.sleep(5)
print("Tarea principal: ¡Completada!")
## Crea dos hilos en segundo plano
print("Creando hilos de monitoreo en segundo plano...")
monitor1 = threading.Thread(target=background_task, args=("Monitor-1", 1), daemon=True)
monitor2 = threading.Thread(target=background_task, args=("Monitor-2", 2), daemon=True)
## Inicia los hilos en segundo plano
monitor1.start()
monitor2.start()
print("Monitores en segundo plano iniciados, ahora iniciando la tarea principal...")
## Ejecuta la tarea principal
main_task()
print("Tarea principal completada, el programa saldrá sin esperar a los hilos demonios.")
print("Los hilos demonios se terminarán cuando el programa salga.")
- Guarde el archivo y ejecútelo:
python3 /home/labex/project/daemon_threads.py
La salida debería ser similar a esto:
Creando hilos de monitoreo en segundo plano...
Monitores en segundo plano iniciados, ahora iniciando la tarea principal...
Tarea principal: Iniciando...
Monitor-1: Iteración 1 a las 14:25:10
Monitor-2: Iteración 1 a las 14:25:10
Monitor-1: Iteración 2 a las 14:25:11
Monitor-1: Iteración 3 a las 14:25:12
Monitor-2: Iteración 2 a las 14:25:12
Monitor-1: Iteración 4 a las 14:25:13
Monitor-1: Iteración 5 a las 14:25:14
Monitor-2: Iteración 3 a las 14:25:14
Tarea principal: ¡Completada!
Tarea principal completada, el programa saldrá sin esperar a los hilos demonios.
Los hilos demonios se terminarán cuando el programa salga.
En este ejemplo:
- Creamos dos hilos demonios que se ejecutan continuamente, imprimiendo mensajes a intervalos regulares.
- Establecimos
daemon=Trueal crear los hilos, lo que los marca como hilos demonios. - El hilo principal se ejecuta durante 5 segundos y luego sale.
- Cuando el hilo principal sale, el programa termina y los hilos demonios también se terminan automáticamente.
Hilos No Demonios vs. Hilos Demonios
Para entender mejor la diferencia, creemos un ejemplo más que compare hilos demonios y no demonios:
Cree un nuevo archivo llamado
daemon_comparison.pyen el directorio/home/labex/project.Agregue el siguiente código:
import threading
import time
def task(name, seconds, daemon=False):
"""Una tarea que se ejecuta durante una cantidad de tiempo especificada."""
print(f"{name} iniciando {'(demonio)' if daemon else '(no-demonio)'}")
time.sleep(seconds)
print(f"{name} finalizado después de {seconds} segundos")
## Crea un hilo no demonio que se ejecuta durante 8 segundos
non_daemon_thread = threading.Thread(
target=task,
args=("Hilo no-demonio", 8, False),
daemon=False ## Este es el valor predeterminado, por lo que en realidad no es necesario
)
## Crea un hilo demonio que se ejecuta durante 8 segundos
daemon_thread = threading.Thread(
target=task,
args=("Hilo demonio", 8, True),
daemon=True
)
## Inicia ambos hilos
non_daemon_thread.start()
daemon_thread.start()
## Deja que el hilo principal se ejecute durante 3 segundos
print("El hilo principal se ejecutará durante 3 segundos...")
time.sleep(3)
## Comprueba qué hilos todavía se están ejecutando
print("\nDespués de 3 segundos:")
print(f"El hilo demonio está vivo: {daemon_thread.is_alive()}")
print(f"El hilo no-demonio está vivo: {non_daemon_thread.is_alive()}")
print("\nEl hilo principal está finalizando. Esto es lo que sucederá:")
print("1. El programa esperará a que todos los hilos no demonios se completen")
print("2. Los hilos demonios se terminarán cuando el programa salga")
print("\nEsperando a que los hilos no demonios finalicen...")
## No necesitamos unir el hilo no demonio, Python esperará por él
## Pero lo uniremos explícitamente para mayor claridad
non_daemon_thread.join()
print("Todos los hilos no demonios han finalizado, el programa saldrá ahora.")
- Guarde el archivo y ejecútelo:
python3 /home/labex/project/daemon_comparison.py
La salida debería ser similar a esto:
Hilo no-demonio iniciando (no-demonio)
Hilo demonio iniciando (demonio)
El hilo principal se ejecutará durante 3 segundos...
Después de 3 segundos:
El hilo demonio está vivo: True
El hilo no-demonio está vivo: True
El hilo principal está finalizando. Esto es lo que sucederá:
1. El programa esperará a que todos los hilos no demonios se completen
2. Los hilos demonios se terminarán cuando el programa salga
Esperando a que los hilos no demonios finalicen...
Hilo no-demonio finalizado después de 8 segundos
Todos los hilos no demonios han finalizado, el programa saldrá ahora.
Observaciones clave:
- Ambos hilos se inician y se ejecutan concurrentemente.
- Después de 3 segundos, ambos hilos todavía se están ejecutando.
- El programa espera a que el hilo no demonio finalice (después de 8 segundos).
- El hilo demonio todavía se está ejecutando cuando el programa sale, pero se termina.
- El hilo demonio nunca llega a imprimir su mensaje de finalización porque se termina cuando el programa sale.
Cuándo Usar Hilos Demonios
Los hilos demonios son útiles para:
- Tareas de monitoreo en segundo plano
- Operaciones de limpieza
- Servicios que deben ejecutarse durante la duración del programa pero no impedir que salga
- Hilos de temporizador que activan eventos a intervalos regulares
Los hilos no demonios son apropiados para:
- Operaciones críticas que deben completarse
- Tareas que no deben ser interrumpidas
- Operaciones que deben finalizar limpiamente antes de que el programa salga
Comprender cuándo usar cada tipo es una parte importante del diseño de aplicaciones robustas con múltiples hilos.
Resumen
En este laboratorio, ha aprendido las técnicas esenciales para trabajar con hilos de Python y cómo esperar a que se completen. Aquí hay un resumen de los conceptos clave cubiertos:
Creación e Inicio de Hilos: Aprendió a crear un objeto de hilo, especificar la función objetivo e iniciar su ejecución con el método
start().Esperando a los Hilos con join(): Descubrió cómo usar el método
join()para esperar a que un hilo se complete antes de continuar con el programa principal, asegurando una sincronización adecuada.Trabajando con Múltiples Hilos: Practicó la creación y gestión de múltiples hilos, tanto manualmente como utilizando la clase
ThreadPoolExecutorpara una gestión de hilos más eficiente.Tiempos de Espera de Hilos y Hilos Demonios: Exploró temas avanzados, incluyendo la configuración de tiempos de espera para las operaciones de hilos y el uso de hilos demonios para tareas en segundo plano.
Estas habilidades proporcionan una base para el desarrollo de aplicaciones con múltiples hilos en Python. El multihilo permite que sus programas realicen múltiples tareas concurrentemente, mejorando el rendimiento y la capacidad de respuesta, especialmente para operaciones vinculadas a E/S (I/O-bound operations).
A medida que continúe trabajando con hilos, recuerde estas mejores prácticas:
- Use hilos para tareas vinculadas a E/S, no para tareas vinculadas a la CPU (considere usar multiprocesamiento para estas últimas)
- Sea consciente de los recursos compartidos y use mecanismos de sincronización apropiados
- Considere usar abstracciones de nivel superior como
ThreadPoolExecutorpara gestionar múltiples hilos - Use hilos demonios para tareas en segundo plano que no deberían impedir que el programa salga
Con estas habilidades y prácticas, ahora está equipado para construir aplicaciones Python más eficientes y con mayor capacidad de respuesta utilizando técnicas de multihilo.



