Saltar al contenido principal

Multiprocesamiento en Python: Guía de hilos y procesos

Aprende a gestionar hilos y procesos con el módulo de multiprocesamiento de Python. Descubre las técnicas clave de la programación paralela. Mejora la eficacia de tu código con ejemplos.
Actualizado 13 dic 2024  · 7 min de lectura

La biblioteca estándar de Python viene equipada con varios paquetes incorporados para que los desarrolladores empiecen a aprovechar las ventajas del lenguaje al instante.

Uno de ellos es el módulo de multiprocesamiento, que permite a los sistemas ejecutar varios procesos simultáneamente. En otras palabras, los desarrolladores pueden dividir las aplicaciones en hilos más pequeños que pueden ejecutarse independientemente de su código Python. A continuación, el sistema operativo asigna estos hilos o procesos al procesador, lo que les permite ejecutarse en paralelo, mejorando el rendimiento y la eficacia de tus programas Python. 

Si palabras como hilos, procesos, procesadores, etc. no te resultan familiares, no te preocupes. En este artículo trataremos la definición de proceso, en qué se diferencian de los hilos y cómo utilizar el módulo multiprocessing

¿Qué son los Procesos en Python?

Entender el concepto de procesos e hilos es extremadamente útil para comprender mejor cómo un sistema operativo gestiona los programas a través de las distintas etapas de ejecución. Un proceso no es más que una referencia a un programa informático. Cada programa tiene asociado un proceso. 

Mientras lees este artículo, es muy probable que tu ordenador tenga muchos procesos en ejecución en un momento dado, aunque sólo tengas unos pocos programas abiertos; esto se debe a que la mayoría de los sistemas operativos tienen varias tareas ejecutándose en segundo plano. Por ejemplo, puede que sólo tengas tres programas en ejecución en este momento, pero tu ordenador puede tener más de 30 procesos activos ejecutándose simultáneamente. 

La forma de comprobar los procesos activos que se están ejecutando en tu ordenador depende de tu sistema operativo: 

  • En Windows: Ctrl+Mayús+Esc iniciará el administrador de tareas 
  • En Mac: abre la Búsqueda Spotlight en Mac y escribe "Monitor de actividad", luego pulsa Retorno  
  • En Linux: haz clic en Menú Aplicación y busca Monitor del Sistema. 

Python proporciona acceso a procesos reales a nivel de sistema. Instanciar una instancia de la clase Process desde el módulo de multiprocesamiento permite a los desarrolladores hacer referencia al proceso nativo subyacente utilizando Python. Se crea un nuevo proceso nativo entre bastidores cuando se inicia un proceso. El ciclo de vida de un proceso Python consta de tres etapas: El inicio de un nuevo proceso, el proceso en marcha y el proceso finalizado: cubriremos cada etapa.

Todos los procesos están formados por uno o varios hilos. ¿Recuerdas que mencionamos que "un proceso no es más que una referencia a un programa informático"? Pues bien, cada programa Python es un proceso que consta de un hilo por defecto llamado hilo principal. El hilo principal es el responsable de ejecutar las instrucciones de tus programas Python. Sin embargo, es importante tener en cuenta que los procesos y los hilos son diferentes. 

Multiprocesamiento vs. Enhebrar

Para ser más concretos, una instancia del intérprete de Python -la herramienta que convierte el código escrito en Python al lenguaje que puede entender un ordenador- equivale a un proceso. Un proceso constará de al menos un hilo, llamado "hilo principal" en Python, aunque pueden crearse otros hilos dentro del mismo proceso: todos los demás hilos creados dentro de un proceso pertenecerán a ese proceso. 

El hilo sirve como representación de cómo se ejecutará tu programa Python, y una vez terminados todos los hilos que no estén en segundo plano, el proceso Python terminará. 

  • Proceso: Un proceso es una instancia del intérprete de Python que consta de al menos un hilo llamado hilo principal. 
  • Tema Una representación de cómo se ejecuta un programa Python dentro de un proceso Python.

Python tiene dos clases muy parecidas que nos permiten un mayor control sobre los procesos y los hilos: multiproceso.Proceso e hilo.Hilo.

Repasemos algunas de sus semejanzas y diferencias. 

Similitudes

Concurrencia

La concurrencia es un concepto en el que diferentes partes del programa pueden ejecutarse fuera de orden o en orden parcial sin que el resultado final se vea afectado. Ambas clases estaban pensadas inicialmente para la concurrencia. 

Soporte para primitivas de concurrencia

El multiprocesamiento.Proceso y roscado. Las clases de hilos admiten las mismas primitivas de concurrencia: una herramienta que permite la sincronización y coordinación de hilos y procesos. 

API uniforme

Una vez que hayas comprendido el multiprocesamiento. Process API, entonces podrás transferir ese conocimiento a la API Threading.Thread, y viceversa. Se diseñaron así intencionadamente. 

Diferencias

Funcionalidad

A pesar de que sus API son iguales, los procesos y los hilos son diferentes. Un proceso es un nivel de abstracción superior a un hilo: un proceso es una referencia a un programa informático, y un hilo pertenece a un proceso. Esta diferencia es inherente a las clases. Así, las clases representan dos funciones nativas diferentes gestionadas por un sistema operativo subyacente. 

Acceso al estado compartido

Las dos clases acceden al estado compartido de forma diferente. Como los hilos pertenecen a un proceso, pueden compartir memoria dentro de un proceso. Así, una función ejecutada en un nuevo hilo sigue teniendo acceso a los mismos datos y estado dentro de un proceso. La forma en que los hilos comparten estados entre sí se conoce como "memoria compartida" y es bastante sencilla. Por el contrario, compartir estados entre procesos es mucho más complicado: el estado debe serializarse y transmitirse entre procesos. En otras palabras, los procesos no utilizan la memoria compartida para compartir estados porque tienen memoria separada. En cambio, los procesos se comparten mediante una técnica llamada "comunicación entre procesos", y para realizarla en Python se necesitan otras herramientas explícitas como multiproceso.Pipe o multiproceso.Queue. 

GIL

El Bloqueo Global del Intérprete de Python (GIL) es un bloqueo que permite que sólo un hilo mantenga el control sobre el intérprete de Python. Los hilos múltiples están sujetos a la GIL, lo que a menudo hace que utilizar Python para realizar multihilos sea una mala idea: la verdadera ejecución multinúcleo mediante multihilos no está soportada por Python en el intérprete CPython. Sin embargo, los procesos no están sujetos al GIL porque éste se utiliza dentro de cada proceso Python, pero no entre procesos. 

En cuanto a los casos de uso, el multiprocesamiento suele eclipsar al roscado en escenarios en los que el programa hace un uso intensivo de la CPU y no es necesario que realice ninguna E/S o interacción con el usuario. Los subprocesos son la mejor solución para los programas que están vinculados a la E/S o a la red, y en situaciones en las que el objetivo es que la aplicación responda mejor. 

Las ventajas del multiprocesamiento en Python

Piensa en un procesador como en un empresario. A medida que el negocio del empresario crece, hay más tareas que deben gestionarse para mantener el ritmo de crecimiento del negocio. Si la empresaria decide asumir sola todas estas tareas (es decir, contabilidad, ventas, marketing, innovación, etc.), corre el riesgo de obstaculizar la eficacia y el rendimiento generales de la empresa, ya que una sola persona sólo puede hacer una cantidad limitada de cosas a la vez. Por ejemplo, antes de pasar a las tareas de innovación, debe detener las tareas de ventas, lo que se conoce como ejecutar las tareas "secuencialmente". 

La mayoría de los empresarios entienden que intentar hacerlo todo solos es una mala idea. En consecuencia, suelen compensar el creciente número de tareas contratando empleados para gestionar varios departamentos. De este modo, las tareas pueden realizarse en paralelo, lo que significa que una tarea no tiene que detenerse para que se ejecute otra. Contratar más empleados para realizar tareas específicas es como utilizar varios procesadores para llevar a cabo las operaciones. Por ejemplo, los proyectos de visión por ordenador son bastante exigentes, ya que normalmente tendrás que procesar muchos datos de imágenes, lo que lleva mucho tiempo: para acelerar este procedimiento, podrías procesar varias imágenes en paralelo. 

Por tanto, podemos decir que el multiprocesamiento sirve para hacer que los programas sean más eficientes, dividiendo y asignando tareas a distintos procesadores. El módulo de multiprocesamiento de Python simplifica esto aún más, sirviendo como herramienta de alto nivel para aumentar la eficiencia de tus programas asignando tareas a diferentes procesos. 

Conceptos básicos del módulo de multiprocesamiento de Python 

Hemos mencionado que el ciclo de vida de un proceso Python consta de tres etapas: el nuevo proceso, el proceso en ejecución y el proceso finalizado. Esta sección profundizará en cada fase del ciclo de vida y proporcionará ejemplos codificados. Puedes acceder al código para seguirlo yendo a nuestro libro de trabajo DataLab

El nuevo proceso 

Un nuevo proceso puede definirse como el proceso que se ha creado instanciando una instancia de la clase Process. Se genera un proceso hijo cuando asignamos el objeto Process a una variable. 

from multiprocessing import Process

# Create a new process
process = Process()

Ahora mismo, nuestra instancia de proceso no está haciendo nada porque hemos inicializado un objeto Proceso vacío. Podríamos alterar las configuraciones de nuestro objeto Proceso para ejecutar una función específica pasando una función que queremos ejecutar en un proceso diferente al parámetro objetivo de la clase. 

# Create a new process with a specified function to execute.
def example_function():
    pass

new_process = Process(target=example_function)

Si nuestra función de destino también tuviera parámetros, simplemente los pasaríamos al parámetro args del objeto Proceso como una tupla.

Consejo: Aprende a escribir funciones en Python con el curso interactivo Escribir funciones en Python

# Create a new process with specific function to execute with args.
def example(args):
    pass

process = Process(target=example, args=("Hi",))

Ten en cuenta que sólo hemos creado un nuevo proceso, pero aún no se está ejecutando. 

Veamos cómo podemos ejecutar un nuevo proceso.  

El proceso en marcha 

Ejecutar un nuevo proceso es bastante sencillo: basta con llamar al método start() de la instancia del proceso. 

# Run the new process
process.start()

 Esta acción inicia la actividad del proceso llamando al método run() de la instancia Proceso bajo el capó. El método run() también es responsable de llamar a la función personalizada especificada en el parámetro de destino de la instancia del Proceso (si se ha especificado).

Recuerda que antes, en el tutorial, dijimos que cada proceso tiene al menos un subproceso llamado subproceso principal, que es el predeterminado. Así, cuando se inicia un proceso hijo, se crea el hilo principal para ese proceso hijo y se inicia. El hilo principal se encarga de ejecutar todo nuestro código en el proceso hijo. 

Podemos comprobar que nuestra instancia de proceso está viva desde que vuelve el método start() hasta que termina el proceso hijo utilizando el método is_alive() en nuestra instancia Process

Nota: Si estás probando esto en un cuaderno, la llamada a is_alive() debe estar en la misma celda que la llamada al método start() para que capte el proceso en ejecución. 

# Run the new process
process.start()

# Check process is running
process.is_alive()

"""
True
"""

Si el proceso no se estuviera ejecutando, la llamada al método is_alive() devolvería False. 

El proceso finalizado

Cuando la función run() vuelve o sale, el proceso se termina: no tenemos que hacer nada explícitamente. En otras palabras, puedes esperar que un proceso termine una vez completadas las instrucciones que establezcas como función objetivo. Por tanto, el método is_alive() devolvería falso. 

# Check process is terminated - should return false.
process.is_alive()

"""
False
"""

Sin embargo, un proceso también puede terminar si encuentra una excepción no controlada o se produce un error. Por ejemplo, si se produce un error en la función que establezcas como objetivo, el proceso finalizará. 

# Create a new process with a specific function that has an error
# to execute with args.
def example(args):
    split_args = list(args.split())
    # "name" variable is not in the function namespace - should raise error
    return name

# New process
process = Process(target=example, args=("Hi",))

# Running the new process
process.start()

# Check process is running
process.is_alive()

"""
True

Process Process-15:
Traceback (most recent call last):
  File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
"""

El objeto Proceso también tiene los métodos terminate() y kill() que permiten a los usuarios terminar un proceso de forma forzada. 

# Create a new process with a specific function to execute with args.
def example(args):
    split_args = list(args.split())
    # "name" variable is not in the function namespace - should raise error
    return split_args

# New process
process = Process(target=example, args=("Hi",))

# Running the new process
process.start()

if process.is_alive():
    process.terminate() # You can also use process.kill()
    print("Process terminated forcefully")

"""
Process terminated forcefully
"""

Es importante tener en cuenta que las terminaciones forzadas no son la forma recomendada de terminar un proceso: puede que no cierren todos los recursos abiertos de forma segura ni almacenen el estado necesario del programa. Una solución mejor es utilizar un apagado controlado con una bandera booleana segura para el proceso o una herramienta similar. 

Tutorial de multiprocesamiento en Python

Ahora que entiendes los fundamentos del multiprocesamiento, vamos a trabajar en un ejemplo para demostrar cómo hacer programación concurrente en Python. 

La función que creamos simplemente imprimirá una declaración, dormirá durante 1 segundo, y luego imprimirá otra dormida - aprende más sobre funciones en este tutorial de funciones de Python

import time

def do_something():
    print("I'm going to sleep")
    time.sleep(1)
    print("I'm awake") 

El primer paso es crear un nuevo proceso: vamos a crear dos. 

# Create new child process

process_1 = Process(target=do_something)
process_2 = Process(target=do_something)

Así es como se verá un programa concurrente en un entorno de cuaderno: 

%%time

# Starts both processes
process_1.start()
process_2.start()

"""
I'm going to sleep
CPU times: user 810 µs, sys: 7.34 ms, total: 8.15 ms
Wall time: 6.04 ms
I'm going to sleep
"""

Dada la salida del programa, es bastante evidente que hay un problema en alguna parte de nuestro código. El temporizador se imprime a mitad de nuestro primer proceso, y la segunda sentencia de impresión no se imprime. 

Esto ocurre porque hay tres procesos en ejecución: el proceso principal, y process_1 y process_2. El proceso que realiza el seguimiento del tiempo y lo imprime es el proceso principal. Para que nuestro proceso principal espere antes de imprimir la hora, debemos llamar al método join() en nuestros dos procesos después de ejecutarlos.

Nota: Si quieres saber más, consulta este debate de Stackoverflow

Veamos nuestro nuevo fragmento de código: 

%%time
​
# Create new child process (Cannot run a process more than once)
new_process_1 = Process(target=do_something)
new_process_2 = Process(target=do_something)
​
# Starts both processes
new_process_1.start()
new_process_2.start()
​
new_process_1.join()
new_process_2.join()

"""
I'm going to sleep
I'm going to sleep
I'm awake
I'm awake
CPU times: user 0 ns, sys: 14 ms, total: 14 ms
Wall time: 1.01 s
"""

Problema resuelto. 

El tiempo de pared de este recorrido fue ligeramente superior al del primer recorrido. Sin embargo, esta ejecución completó las llamadas a nuestras dos funciones objetivo antes de devolver la información horaria. También podemos aplicar este mismo razonamiento para hacer que más procesos se ejecuten simultáneamente. 

Reflexiones finales sobre el multiprocesamiento en Python

En este tutorial, has aprendido cómo hacer que los programas Python sean más eficientes ejecutándolos de forma concurrente. Concretamente, aprendiste: 

  • Qué son los procesos y cómo puedes verlos en tu ordenador.
  • Las similitudes y diferencias entre los módulos de multiprocesamiento y de hilos de Python.
  • Los fundamentos del módulo de multiprocesamiento y cómo ejecutar un programa Python de forma concurrente utilizando el multiprocesamiento. 

¿Quieres saber más sobre Programación en Python? Consulta nuestro itinerario profesional de Programador de Python. No se requiere experiencia previa en programación y, al final del curso, habrás adquirido las habilidades necesarias para desarrollar con éxito software, manejar datos y realizar análisis avanzados en Python. 

Conviértete en Desarrollador Python

Adquiere los conocimientos de programación que necesitan todos los desarrolladores de Python.
Empieza a aprender gratis

Kurtis Pykes 's photo
Author
Kurtis Pykes
LinkedIn
Temas

Cursos para Python

curso

Intermediate Python

4 hr
1.2M
Level up your data science skills by creating visualizations using Matplotlib and manipulating DataFrames with pandas.
Ver detallesRight Arrow
Comienza el curso
Ver másRight Arrow
Relacionado

tutorial

Una Introducción al Subproceso Python: Conceptos básicos y ejemplos

Explora nuestra guía paso a paso para ejecutar comandos externos utilizando el módulo de subprocesos de Python, completa con ejemplos.
Moez Ali's photo

Moez Ali

15 min

tutorial

Tutorial sobre cómo trabajar con módulos en Python

Los módulos te permiten dividir partes de tu programa en archivos diferentes para facilitar el mantenimiento y mejorar el rendimiento.

Nishant Kumar

8 min

tutorial

Tutorial de Pickle en Python: Serialización de objetos

Descubre el módulo pickle de Python: aprende sobre serialización, cuándo (no) usarla, cómo comprimir objetos pickle, multiprocesamiento ¡y mucho más!
Natassha Selvaraj's photo

Natassha Selvaraj

16 min

tutorial

Tutorial de Excel en Python: La guía definitiva

Aprende a leer e importar archivos Excel en Python, a escribir datos en estas hojas de cálculo y a encontrar los mejores paquetes para hacerlo.
Natassha Selvaraj's photo

Natassha Selvaraj

30 min

tutorial

Optimización en Python: Técnicas, Paquetes y Buenas Prácticas

Este artículo te enseña la optimización numérica, destacando diferentes técnicas. Analiza paquetes de Python como SciPy, CVXPY y Pyomo, y proporciona un práctico cuaderno DataLab para ejecutar ejemplos de código.
Kurtis Pykes 's photo

Kurtis Pykes

19 min

tutorial

Aprendizaje automático de datos categóricos con el tutorial de Python

Aprenda los trucos más comunes para manejar datos categóricos y preprocesarlos para construir modelos de aprendizaje automático.
Moez Ali's photo

Moez Ali

28 min

See MoreSee More