# Python multiprocessing: ejecución de procesos en paralelo

Python multiprocessing te permite dividir la carga de trabajo en varios procesos y reducir así el tiempo total de ejecución. Esto supone una gran ventaja, especialmente cuando deben realizarse muchos cálculos o cuando hay grandes conjuntos de datos.

## ¿Qué es Python multiprocessing?

El multiprocesamiento en Python se refiere a la capacidad de ejecutar varios procesos a la vez para aprovechar al máximo el rendimiento de los **sistemas de núcleo múltiple**. A diferencia de los sistemas de un solo hilo, en los que las tareas se ejecutan de forma secuencial, el multiprocesamiento permite que diferentes partes del programa se ejecuten en paralelo e independientemente. Cada proceso tiene su propio espacio de memoria y se puede procesar en núcleos de procesador separados, lo que reduce significativamente el tiempo de ejecución para operaciones que requieren muchos cálculos o tienen requisitos de tiempo estrictos.

El Python multiprocessing puede utilizarse en el procesamiento y análisis de datos, por ejemplo, para procesar grandes conjuntos de datos de manera más rápida. También puede emplearse en **simulaciones y cálculos de modelado**, como en aplicaciones científicas, para reducir los tiempos de ejecución de cálculos complejos. Además, el multiprocesamiento se utiliza en el ámbito del web scraping para recopilar datos de varias páginas web simultáneamente, así como en el procesamiento de imágenes y visión por computadora para mejorar la eficiencia de las operaciones de análisis.

## ¿Dónde se puede implementar el multiprocesamiento en Python?

Python ofrece varias posibilidades de implementar el multiprocesamiento. A continuación, te presentamos tres herramientas habituales: el módulo `multiprocessing`, la biblioteca `concurrent.futures` y el paquete `joblib`.

### El módulo `multiprocessing`

`Multiprocessing` es el **módulo estándar para el multiprocesamiento de Python**. Con él es posible crear procesos, intercambiar datos entre esos procesos y realizar sincronizaciones mediante bloqueos, colas y otros mecanismos.

```python
import multiprocessing
def task(n):
    result = n * n
    print(f"Result: {result}")
if __name__ == "__main__":
    processes = []
    for i in range(1, 6):
        process = multiprocessing.Process(target=task, args=(i,))
        processes.append(process)
        process.start()
    for process in processes:
        process.join()
```

En el anterior ejemplo se ha utilizado la clase `multiprocessing.Process` para crear e iniciar procesos que ejecutan la función `task()`. Esta función toma el número transferido y lo eleva al cuadrado. Después, se inicia cada proceso y se espera a que terminen antes de continuar con el programa principal. El resultado se obtiene con un **f-string**, que es un método de [Python string format](https://www.ionos.com/es-us/digitalguide/paginas-web/desarrollo-web/python-string-format/) para enlazar expresiones. La secuencia de salida de los resultados no sigue un orden específico y puede variar en cada ejecución.

También puedes crear un pool de procesos con Python `multiprocessing` de la siguiente manera:

```python
import multiprocessing
def task(n):
    return n * n
if __name__ == "__main__":
    with multiprocessing.Pool() as pool:
        results = pool.map(task, range(1, 6))
        print(results)  # Output: [1, 4, 9, 16, 25]
```

Con `pool.map()` se aplica la función `task()` a una secuencia de datos, y se recopilan y devuelven los resultados.

### La biblioteca `concurrent.futures`

El módulo `concurrent.futures` proporciona una interfaz de alto nivel para la ejecución asíncrona y el procesamiento en paralelo de tareas. Utiliza el **Pool Executor** para ejecutar tareas en un pool de procesos o hilos. Este módulo ofrece una manera más sencilla de manejar tareas asíncronas y, en muchos casos, es más fácil de usar que el módulo multiprocessing de Python.

```python
import concurrent.futures
def task(n):
    return n * n
with concurrent.futures.ProcessPoolExecutor() as executor:
    futures = [executor.submit(task, i) for i in range(1, 6)]
    for future in concurrent.futures.as_completed(futures):
        print(future.result()) # result in random order
```

El código utiliza el módulo `concurrent.futures` para procesar tareas en paralelo con el `ProcessPoolExecutor`. La función `task(n)` se transfiere para números del 1 al 5. El método `as_completed()` espera a que se completen las tareas y devuelve los **resultados en un orden aleatorio**.

### `joblib`

`joblib` es una biblioteca externa de Python diseñada para simplificar el procesamiento en paralelo, por ejemplo, para tareas repetitivas como ejecutar funciones con diferentes parámetros de entrada o trabajar con grandes cantidades de datos. Las principales funciones de `joblib` son paralelizar tareas, **almacenar en caché los resultados de las funciones** y optimizar los recursos de memoria y computación.

```python
from joblib import Parallel, delayed
def task(n):
    return n * n
results = Parallel(n_jobs=4)(delayed(task)(i) for i in range(1, 11))
print(results) # Output: Results of the function for numbers from 1 to 10
```

Con la expresión `Parallel(n_jobs=4)(delayed(task)(i) for i in range(1, 11))` se inicia la ejecución en paralelo de la función `task()` para los números del 1 al 10. `Parallel` está configurado con el argumento `n_jobs=4`, lo que indica que se pueden procesar hasta cuatro trabajos en paralelo. Al llamar a `delayed(task)(i)`, se crea la tarea que debe ejecutarse en paralelo para cada número i en el rango del 1 al 10. Es decir, se llama a la función `task()` simultáneamente para cada uno de esos números. El resultado para los números del 1 al 10 se almacena en `results` y se imprime.


This is a markdown version of: [https://www.ionos.com/es-us/digitalguide/paginas-web/desarrollo-web/python-multiprocessing/](https://www.ionos.com/es-us/digitalguide/paginas-web/desarrollo-web/python-multiprocessing/) for AI/LLM consumption.