Módulo Python threading
de PythonthreadingEl módulo es una de las bibliotecas estándar para implementar programación multihilo. El multihilo permite que un programa ejecute múltiples tareas al mismo tiempo, mejorando así su eficiencia y capacidad de respuesta.
threadingEl módulo proporciona herramientas para crear y administrar hilos, lo que permite a los desarrolladores escribir programas concurrentes con facilidad.
¿Qué es un hilo?
Puedes imaginar los hilos como empleados en una oficina:
-
Un programa de un solo hilo es como tener un único empleado que debe completar secuencialmente tareas como imprimir documentos, responder correos electrónicos, preparar café, etc.
-
Un programa multihilo, en cambio, es como tener varios empleados, que puedensimultáneamenterealizar diferentes tareas, aumentando en gran medida la eficiencia del trabajo.
En ciencias de la computación:
- Proceso: un programa en ejecución con espacio de memoria independiente (por ejemplo, el navegador y el reproductor de música que tienes abiertos al mismo tiempo son dos procesos).
- Hilo: un flujo de ejecución independiente dentro de un proceso, es la unidad básica de planificación de la CPU. Todos los hilos dentro del mismo procesocomparten el espacio de memoria de ese proceso(como las variables globales).
¿Por qué usar multihilo?
En un programa de un solo hilo, las tareas se ejecutan secuencialmente una tras otra. Si una tarea necesita esperar (por ejemplo, esperar una respuesta de red o una lectura de archivo), todo el programa se bloquea hasta que dicha tarea se completa. En cambio, los hilos múltiples permiten que el programa continúe ejecutando otras tareas mientras espera una tarea determinada, mejorando así el rendimiento general del programa.
Los hilos de Python y el bloqueo global del intérprete (GIL)
Python tiene un mecanismo llamado Bloqueo Global del Intérprete (Global Interpreter Lock, GIL). El GIL garantiza que en cualquier momento solo un hilo pueda ejecutar bytecode de Python.
¿Qué significa esto?Para tareas de uso intensivo de CPU (como cálculos científicos, procesamiento de imágenes), debido a la existencia del GIL, el multihilo normalmente no puede aprovechar las ventajas de múltiples núcleos para aumentar la velocidad de cálculo, e incluso puede volverse más lento por la sobrecarga del cambio de hilos.
Entonces, ¿dónde está el lugar adecuado para el multihilo en Python?Para tareas de uso intensivo de E/S (como solicitudes de red, lectura y escritura de archivos, espera de entrada del usuario), los hilos liberan el GIL mientras esperan que se complete la operación de E/S, permitiendo que otros hilos se ejecuten. Esto puede mejorar significativamente la velocidad de respuesta y la eficiencia generales del programa, porque mientras esperas la respuesta de una página web, el programa puede ocuparse de otra tarea.
¿Cómo usar el módulo threading?
Usarthreadingel primer paso es importarlo:
import threading import time # 用于模拟耗时操作
La forma más básica de crear un hilo es usarthreading.Threadclase.
Descripción de la sintaxis:
thread_obj = threading.Thread(target=函数名, args=(参数元组,))
- target: Especifica la función que se ejecutará después de que el hilo se inicie.
- args: Los argumentos pasados a la función `target` deben ser de tipo tupla. Si solo hay un parámetro, se debe escribir como
(parámetro,)de la forma.
1. Crear hilos
En Python, se puede heredar dethreading.Threadla clase o usar directamentethreading.Threadel constructor para crear hilos.
Método 1: Herenciathreading.ThreadClase
Ejemplo
class MyThread(threading.Thread):
def run(self):
print("El hilo comienza a ejecutarse")
# Escribe aquí el código que ejecutará el hilo
print("El hilo terminó de ejecutarse")
# Crear una instancia del hilo
thread = MyThread()
# Iniciar el hilo
thread.start()
# Esperar a que el hilo termine su ejecución
thread.join()
print("El hilo principal ha finalizado")
Método 2: Usarthreading.Threadconstructor
Ejemplo
def my_function():
print("El hilo comienza a ejecutarse")
# Escribe aquí el código que ejecutará el hilo
print("El hilo terminó de ejecutarse")
# Crear una instancia del hilo
thread = threading.Thread(target=my_function)
# Iniciar el hilo
thread.start()
# Esperar a que el hilo termine su ejecución
thread.join()
print("El hilo principal ha finalizado")
2. Sincronización de hilos
En la programación multihilo, varios hilos pueden acceder a recursos compartidos simultáneamente, lo que puede causar problemas de inconsistencia de datos. Para evitar esta situación, se pueden usar mecanismos de sincronización de hilos, como un bloqueo (Lock)。
Ejemplo
# Crear un objeto de bloqueo
lock = threading.Lock()
def my_function():
with lock:
print("El hilo comienza a ejecutarse")
# Escribe aquí el código que ejecutará el hilo
print("El hilo terminó de ejecutarse")
# Crear una instancia del hilo
thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)
# Iniciar el hilo
thread1.start()
thread2.start()
# Esperar a que el hilo termine su ejecución
thread1.join()
thread2.join()
print("El hilo principal ha finalizado")
3. Comunicación entre hilos
La comunicación entre hilos se puede implementar mediante una cola (queueQueue).Queuees seguro para hilos y puede transmitir datos de manera segura entre varios hilos.
Ejemplo
import queue
def worker(q):
while not q.empty():
item = q.get()
print(f"Procesando elemento: {item}")
q.task_done()
# Crear una cola y llenarla con datos
q = queue.Queue()
for i in range(10):
q.put(i)
# Crear una instancia del hilo
thread1 = threading.Thread(target=worker, args=(q,))
thread2 = threading.Thread(target=worker, args=(q,))
# Iniciar el hilo
thread1.start()
thread2.start()
# Esperar a que se procesen todos los elementos de la cola
q.join()
print("Todos los elementos han sido procesados")
Clases, métodos y atributos comunes
1. Clases principales
| Clase/Método/Atributo | Descripción | Ejemplo |
|---|---|---|
threading.Thread | Clase de hilo, utilizada para crear y gestionar hilos | t = Thread(target=func, args=(1,)) |
threading.Lock | Mutex (bloqueo primitivo) | lock = Lock() |
threading.RLock | Bloqueo reentrante (el mismo hilo puede adquirirlo varias veces) | rlock = RLock() |
threading.Event | Objeto de evento, utilizado para la sincronización de hilos | event = Event() |
threading.Condition | Variable de condición, utilizada para la coordinación compleja de hilos | cond = Condition() |
threading.Semaphore | Semáforo, controla el número de hilos concurrentes | sem = Semaphore(3) |
threading.BoundedSemaphore | Semáforo acotado (evita que el contador supere el valor inicial) | b_sem = BoundedSemaphore(2) |
threading.Timer | Temporizador de hilo, para ejecución diferida | timer = Timer(5.0, func) |
threading.local | Datos locales de hilo (almacenamiento independiente por hilo) | local_data = threading.local() |
2. Métodos/atributos comunes del objeto Thread
| Método/Atributo | Descripción | Ejemplo |
|---|---|---|
start() | Iniciar el hilo | t.start() |
run() | Método que ejecuta el hilo (se puede sobrescribir) | Sobrescribe este método al definir una clase personalizada |
join(timeout=None) | Bloquea el hilo actual hasta que el hilo objetivo termine | t.join() |
is_alive() | Comprueba si el hilo está en ejecución | if t.is_alive(): |
name | Nombre del hilo (modificable) | t.name = "Worker-1" |
daemon | Indicador de hilo daemon (finaliza automáticamente cuando el hilo principal termina) | t.daemon = True |
ident | Identificador del hilo (cuando no se ha iniciado, esNone) | print(t.ident) |
3. Métodos comunes de Lock/RLock
| Método | Descripción | Ejemplo |
|---|---|---|
acquire(blocking=True, timeout=-1) | Adquiere el bloqueo (bloqueante o no bloqueante) | lock.acquire() |
release() | Libera el bloqueo | lock.release() |
locked() | Comprueba si el bloqueo está ocupado | if not lock.locked(): |
4. Métodos comunes de Event
| Método | Descripción | Ejemplo |
|---|---|---|
set() | Establece el evento como verdadero y despierta a todos los hilos en espera | event.set() |
clear() | Restablece el evento como falso | event.clear() |
wait(timeout=None) | Bloquea hasta que el evento sea verdadero o se agote el tiempo | event.wait(2.0) |
is_set() | Comprueba el estado del evento | if event.is_set(): |
5. Métodos comunes de Condition
| Método | Descripción | Ejemplo |
|---|---|---|
wait(timeout=None) | Libera el bloqueo y se bloquea hasta que reciba una notificación o se agote el tiempo | cond.wait() |
notify(n=1) | Despierta como máximonhilos en espera | cond.notify(2) |
notify_all() | Despierta a todos los hilos en espera | cond.notify_all() |
6. Funciones/atributos a nivel de módulo
| Función/Atributo | Descripción | Ejemplo |
|---|---|---|
threading.active_count() | Devuelve el número de hilos activos actuales | print(threading.active_count()) |
threading.current_thread() | Devuelve el objeto del hilo actual | print(threading.current_thread().name) |
threading.enumerate() | Devuelve una lista de todos los hilos activos | for t in threading.enumerate(): |
threading.main_thread() | Devuelve el objeto del hilo principal | if threading.current_thread() is threading.main_thread(): |
threading.get_ident() | Devuelve el identificador del hilo actual (Python 3.3+) | print(threading.get_ident()) |
Ejemplo
1. Creación básica de hilos
Ejemplo
def worker(num):
print(f"Worker {num} started")
threads = []
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
2. Uso de bloqueos para proteger recursos compartidos
Ejemplo
count = 0
def increment():
global count
with lock: # Adquirir y liberar el bloqueo automáticamente
count += 1
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(count) # Salida: 10
3. Sincronización de eventos
Ejemplo
def waiter():
print("Waiting for event...")
event.wait()
print("Event triggered!")
t = threading.Thread(target=waiter)
t.start()
# El hilo principal dispara el evento
threading.Event().wait(2.0) # Simular retraso
event.set()
t.join()
4. Modelo productor-consumidor (Condition)
Ejemplo
from threading import Condition
queue = []
cond = Condition()
MAX_ITEMS = 5
def producer():
for _ in range(10):
with cond:
while len(queue) >= MAX_ITEMS:
cond.wait()
item = random.randint(1, 100)
queue.append(item)
print(f"Produced {item}")
cond.notify()
def consumer():
for _ in range(10):
with cond:
while not queue:
cond.wait()
item = queue.pop(0)
print(f"Consumed {item}")
cond.notify()
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()
Consideraciones
Bloqueo global del intérprete (GIL): El GIL de Python limita la ejecución del código de bytes de Python a un solo hilo a la vez. Por lo tanto, en tareas intensivas en CPU, el uso de múltiples hilos puede no mejorar el rendimiento. Para tareas intensivas en E/S, los hilos siguen siendo beneficiosos.
Seguridad de hilos: En un entorno de subprocesos múltiples, asegúrate de que el acceso a los recursos compartidos sea seguro para los hilos, evitando condiciones de carrera e interbloqueos.
Cantidad de hilos: Crear demasiados hilos puede agotar los recursos del sistema y afectar el rendimiento del programa. Controla razonablemente el número de hilos o usa un grupo de hilos (
ThreadPoolExecutor) para gestionar los hilos.