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

import threading

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

import threading

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

import threading

# 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 threading
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/AtributoDescripciónEjemplo
threading.ThreadClase de hilo, utilizada para crear y gestionar hilost = Thread(target=func, args=(1,))
threading.LockMutex (bloqueo primitivo)lock = Lock()
threading.RLockBloqueo reentrante (el mismo hilo puede adquirirlo varias veces)rlock = RLock()
threading.EventObjeto de evento, utilizado para la sincronización de hilosevent = Event()
threading.ConditionVariable de condición, utilizada para la coordinación compleja de hiloscond = Condition()
threading.SemaphoreSemáforo, controla el número de hilos concurrentessem = Semaphore(3)
threading.BoundedSemaphoreSemáforo acotado (evita que el contador supere el valor inicial)b_sem = BoundedSemaphore(2)
threading.TimerTemporizador de hilo, para ejecución diferidatimer = Timer(5.0, func)
threading.localDatos locales de hilo (almacenamiento independiente por hilo)local_data = threading.local()

2. Métodos/atributos comunes del objeto Thread

Método/AtributoDescripciónEjemplo
start()Iniciar el hilot.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 terminet.join()
is_alive()Comprueba si el hilo está en ejecuciónif t.is_alive():
nameNombre del hilo (modificable)t.name = "Worker-1"
daemonIndicador de hilo daemon (finaliza automáticamente cuando el hilo principal termina)t.daemon = True
identIdentificador del hilo (cuando no se ha iniciado, esNone)print(t.ident)

3. Métodos comunes de Lock/RLock

MétodoDescripciónEjemplo
acquire(blocking=True, timeout=-1)Adquiere el bloqueo (bloqueante o no bloqueante)lock.acquire()
release()Libera el bloqueolock.release()
locked()Comprueba si el bloqueo está ocupadoif not lock.locked():

4. Métodos comunes de Event

MétodoDescripciónEjemplo
set()Establece el evento como verdadero y despierta a todos los hilos en esperaevent.set()
clear()Restablece el evento como falsoevent.clear()
wait(timeout=None)Bloquea hasta que el evento sea verdadero o se agote el tiempoevent.wait(2.0)
is_set()Comprueba el estado del eventoif event.is_set():

5. Métodos comunes de Condition

MétodoDescripciónEjemplo
wait(timeout=None)Libera el bloqueo y se bloquea hasta que reciba una notificación o se agote el tiempocond.wait()
notify(n=1)Despierta como máximonhilos en esperacond.notify(2)
notify_all()Despierta a todos los hilos en esperacond.notify_all()

6. Funciones/atributos a nivel de módulo

Función/AtributoDescripciónEjemplo
threading.active_count()Devuelve el número de hilos activos actualesprint(threading.active_count())
threading.current_thread()Devuelve el objeto del hilo actualprint(threading.current_thread().name)
threading.enumerate()Devuelve una lista de todos los hilos activosfor t in threading.enumerate():
threading.main_thread()Devuelve el objeto del hilo principalif 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

import threading

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

lock = threading.Lock()
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

event = threading.Event()

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

import random
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

  1. 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.

  2. 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.

  3. 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.


Cuestionario

Cuestionario sobre el módulo threading

Pregunta 1
1. ¿A qué tipo de tareas de subprocesos múltiples afecta principalmente el bloqueo global del intérprete (GIL) de Python?
Pregunta 2
2. Después de crear el objeto de hilo, ¿qué método se debe llamar para iniciar la ejecución del hilo?
Pregunta 3
3. Cuando varios hilos necesitan modificar la misma variable global, ¿qué se debe usar para evitar la corrupción de datos?
Pregunta 4
4. ¿Cuál es el propósito de que el hilo principal use thread.join()?
Otras extensiones