Saltar a contenido

Librería Thread

Es similar a la libreria thread de C++, requiere como parametros la función a paralelizar y sus argumentos.

La función a paralelizar debe implementar la estrategia de paralelización. La estrategia utiliza es partir el rango usando los argumentos de entrada ini y fin

"""
Autor: Carlos A Delgado
Fecha: 23 de Sep de 2025
Ejemplo de uso de threading en Python
"""
from threading import Thread
import time

# Función que genera números y los añade a una lista
def generador(ini, fin, l):
    for i in range(ini, fin):
        l.append(10)  # Añade el valor 10 a la lista 'l' en cada iteración

if __name__ == "__main__":
    n = 10000000  # Número total de elementos a generar

    # Ejecución secuencial (sin hilos)
    ini = time.time()  # Marca tiempo inicial
    l = []  # Lista vacía
    generador(0, n, l)  # Llama a la función con todo el rango
    fin = time.time()  # Marca tiempo final

    # Muestra primeros 20 elementos, longitud, suma total y tiempo
    print(l[:20], len(l), sum(l), "Tiempo en s " + str(fin-ini))

    # Ejecución con 4 hilos
    l1, l2, l3, l4 = [], [], [], []  # Cuatro listas para los hilos
    ini = time.time()  # Marca tiempo inicial para ejecución paralela

    # Creación de hilos dividiendo el trabajo en 4 partes
    t1 = Thread(target=generador, args=(0, n//4, l1))      # Hilo 1: 0 a n/4
    t2 = Thread(target=generador, args=(n//4, n//2, l2))   # Hilo 2: n/4 a n/2
    t3 = Thread(target=generador, args=(n//2, 3*n//4, l3)) # Hilo 3: n/2 a 3n/4
    t4 = Thread(target=generador, args=(3*n//4, n, l4))    # Hilo 4: 3n/4 a n

    # Inicia la ejecución de los hilos
    t1.start()
    t2.start()
    t3.start()
    t4.start()

    # Espera a que todos los hilos terminen
    t1.join()
    t2.join()
    t3.join()
    t4.join()

    fin = time.time()  # Marca tiempo final
    r = l1 + l2 + l3 + l4  # Combina resultados de los hilos
    # Muestra longitud, suma total y tiempo de ejecución paralela
    print(len(r), sum(r), "El tiempo en ms es " + str(fin-ini))

Los hilos en Python permiten ejecutar múltiples tareas concurrentemente dentro del mismo proceso. Cada hilo comparte el mismo espacio de memoria, lo que facilita el intercambio de datos pero requiere sincronización para evitar condiciones de carrera. El Global Interpreter Lock (GIL) en CPython limita la ejecución paralela real de código Python, pero los hilos siguen siendo útiles para operaciones de E/S o cuando se mezclan con extensiones que liberan el GIL.

En el código, los conceptos de hilos se aplican mediante: la creación de objetos Thread con target=generador para definir la función a ejecutar; args para pasar parámetros específicos a cada hilo; start() para iniciar la ejecución concurrente; y join() para sincronizar la finalización. La división del trabajo (n//4, n//2, etc.) demuestra cómo paralelizar una tarea computacionalmente intensiva, aunque el GIL puede limitar la ganancia de rendimiento en operaciones CPU-bound puras. Las listas l1, l2, l3, l4 actú como memoria compartida donde cada hilo escribe su resultado parcial.

ThreadPool

El threadpool es una colección de hilos, los cuales indicamos cual es su tamaño máximo, cuando enviamos una tarea con submit esta es asignada a uno de los hilos disponibles. Se debe esperar a que todos los hilos se terminen, esto lo hacemos con shutdown

"""
Autor: Carlos A Delgado
Fecha: 23 de Sep de 2025
Ejemplo de uso de thread pool en Python
"""

from concurrent.futures import ThreadPoolExecutor
import time


def generador(ini, fin, l):
    for i in range(ini, fin):
        l.append(10)  # Añade el valor 10 a la lista 'l' en cada iteración


if __name__ == "__main__":
    n = 10000000  # Número total de elementos a generar
    num_thread = 16  # Número de hilos en el pool

    # Ejecución secuencial (sin hilos)
    ini = time.time()
    l = []
    generador(0, n, l)
    fin = time.time()

    print(l[:20], len(l), sum(l), "Tiempo en s " + str(fin - ini))

    # Ejecución con ThreadPoolExecutor

    ini = time.time()
    pool = ThreadPoolExecutor(max_workers=num_thread)  # Crea pool con 16 hilos
    cnt = 0
    l = []  # Lista para almacenar las listas de resultados de cada hilo

    for i in range(0, num_thread):
        s = []  # Nueva lista para cada tarea
        # Envía tarea al pool: divide el trabajo en num_thread partes
        pool.submit(generador, cnt * n // num_thread, (cnt + 1) * n // num_thread, s)
        cnt += 1
        l.append(s)  # Almacena la lista de resultados

    fin = time.time()
    pool.shutdown(wait=True)  # Espera a que todas las tareas terminen

    r = []
    for i in range(0, num_thread):
        r += l[i]  # Combina todos los resultados
    print(len(r), sum(r), "El tiempo en ms es " + str(fin - ini))

El ThreadPoolExecutor gestiona un grupo de hilos pre-inicializados que reutiliza para tareas, evitando el costo de crear y destruir hilos repetidamente. El método submit() encola tareas que son ejecutadas por los hilos disponibles en el pool. El parámetro max_workers=16 define el máximo de hilos concurrentes.

El shutdown(wait=True) asegura que el programa espere a la finalización de todas las tareas antes de continuar, similar al join() en threading.Thread.

La división del trabajo en 16 partes iguales mediante cnt * n // num_thread demuestra la distribución de carga, pero la implementación actual no garantiza la integridad de los datos debido al problema de concurrencia con listas no sincronizadas.