El threading permite que distintas partes de tu proceso se ejecuten de forma concurrente (Fuente: RealPython). Estas partes suelen ser independientes y tienen una unidad de ejecución separada que pertenece al mismo proceso. Un proceso no es más que un programa en ejecución que contiene unidades que pueden ejecutarse en paralelo. Por ejemplo, un navegador web puede ser un proceso; una aplicación que usa varias cámaras a la vez puede ser otro proceso; un videojuego es otro ejemplo de proceso.
Dentro de un proceso aparece el concepto de múltiples hilos, o multithreading, donde varios hilos colaboran para alcanzar un objetivo común. La gran ventaja de usar hilos es que te permite ejecutar el programa en paralelo.
Veamos el concepto de threading con un ejemplo. Imagina una aplicación que cuenta los coches que entran y salen del aparcamiento de un centro comercial. El sistema tiene varias cámaras conectadas a un dispositivo central que monitorizan entradas y salidas. Cada cámara ejecuta un algoritmo para vigilar el flujo de coches y forma parte del mismo proceso o programa. Sin embargo, cada cámara, junto con el algoritmo que ejecuta, podría estar en un hilo independiente. Es más, la lectura de fotogramas de la cámara y la inferencia del algoritmo sobre esos fotogramas podrían ser dos hilos distintos.
Otro ejemplo sería un videojuego en el que el proceso necesita ejecutar tareas en paralelo como los gráficos, la interacción con el usuario y la red (en partidas multijugador) para ser siempre responsivo. Para lograrlo, recurre al multithreading, asignando a cada hilo la responsabilidad de una tarea independiente.
Un hilo tiene su propio flujo de ejecución, lo que significa que en el proceso suceden varias cosas a la vez.
Ten en cuenta que todo proceso tiene al menos un hilo, llamado main thread (hilo principal). Si tu programa no define ningún hilo adicional, al menos contará con el hilo principal, es decir, el propio proceso.
Si observas el diagrama superior, hay un proceso con el hilo principal que contiene variables globales y tres hilos diferentes t1, t2 y t3. Cada hilo tiene sus variables locales y su flujo de ejecución. Los hilos comparten en general los recursos del proceso; es decir, los recursos disponibles para el hilo principal también se comparten con los hilos t1, t2 y t3. Todos los hilos pueden acceder a las variables globales y, además, cuentan con sus variables locales.
Existen distintos tipos de hilos:
Hilos de kernel
Hilos de usuario
Combinación de hilos de kernel y de usuario
Veamos ahora algunas ventajas de usar threading en tu programa:
El multithreading puede acelerar la ejecución si dispones de múltiples CPU.
También te permite realizar otras tareas mientras se ejecutan operaciones de E/S (I/O) gracias a varios hilos o incluso con el hilo principal más un hilo. Por ejemplo, la lectura de fotogramas de la cámara y la inferencia del algoritmo se gestionarán en hilos diferentes. Así, el algoritmo no tendrá que esperar a que llegue un fotograma y la lectura de fotogramas no tendrá que esperar a que termine la ejecución del algoritmo para leer el siguiente.
Los hilos dentro de un mismo proceso pueden compartir la memoria y los recursos del hilo principal.
Retos del threading
Recuerda que Python, en su implementación CPython, limita la ejecución a un solo hilo a la vez; por tanto, threading no acelera todas las tareas. La razón principal es el Global Interpreter Lock (GIL).
Si quieres aprender más sobre el GIL, echa un vistazo a este tutorial.
Si buscas acelerar tareas intensivas de CPU, threading puede no ser la mejor solución. En esos casos, suele ser más útil el multiprocesamiento.
Para ejecutar un programa con muchos hilos, es necesario cambiar de uno a otro con rapidez; por eso, la planificación (scheduling) es clave en programas con muchos hilos.
El uso compartido de recursos también puede ser problemático, ya que todos los hilos comparten la misma memoria y las variables globales. Una operación en un hilo podría provocar un error de memoria en otro, o impedir que otro hilo obtenga memoria para realizar su tarea.
Threading en Python
En Python, el módulo de hilos es un módulo integrado llamado threading y puede importarse directamente.
Como casi todo en Python se representa como objeto, los hilos también son objetos. Un hilo es capaz de:
Almacenar datos,
Guardarse en estructuras como diccionarios, listas, sets, etc.
Pasarse como parámetro a una función.
Un hilo también puede ejecutarse como un proceso.
Un hilo en Python puede estar en varios estados, como:
Espera (wait),
Bloqueado (locked).
Veamos ahora cómo implementar threading en Python.
Módulo thread en Python 3
Ten en cuenta que Python 3 mantiene compatibilidad con el módulo thread de Python 2.7. En Python 3 puede importarse como módulo _thread. Veamos un ejemplo para entender el módulo _thread.
import _thread #thread module importedimport time #time moduleCon la tecnología de
Definamos una función llamada thread_delay que recibe dos parámetros: el nombre del hilo y el retardo. Dentro de esta función:
Defines un contador que empieza en cero,
Luego iteras con un bucle while que se ejecutará tres veces,
Dentro del while llamas a time.sleep() para añadir un retardo; esto te ayudará a ver cómo se ejecuta el hilo. El retardo se expresa en segundos,
Después incrementas el contador en 1,
Para comprobar que el hilo se ejecuta bien, imprimes el nombre del hilo y la hora a la que se ejecuta.
defthread_delay(thread_name, delay):
count =0while count <3:
time.sleep(delay)
count +=1print(thread_name,'-------->', time.time())Con la tecnología de
Ahora, para ejecutar la función anterior en un hilo, usa el método start_new_thread, que pertenece al módulo _thread.
Pasa la función thread_delay sin paréntesis y dos argumentos: el nombre del hilo y el retardo (para poder visualizar la ejecución, ya que es muy rápida).
_thread.start_new_thread(thread_delay,('t1',1))
_thread.start_new_thread(thread_delay,('t2',3))Con la tecnología de
10916
t1 -------->1588077856.4221454
t1 -------->1588077857.4418247
t2 -------->1588077858.422453
t1 -------->1588077858.4431
t2 -------->1588077861.4371448
t2 -------->1588077864.438915Con la tecnología de
En la salida puedes ver que el hilo t1 empieza a ejecutarse primero. Mientras tanto, t2 espera por su retardo de 3 segundos y, en cuanto termina, se ejecuta. El retardo de t1 es de solo 1 segundo.
Cambiemos ahora el retardo de t2 a 5 segundos, de modo que t2 empiece cuando t1 ya haya terminado, ya que t1 tardará solo 3 segundos en completarse.
_thread.start_new_thread(thread_delay,('t1',1))
_thread.start_new_thread(thread_delay,('t2',5))Con la tecnología de
14632
t1 -------->1588078001.4087512
t1 -------->1588078002.4272866
t1 -------->1588078003.432218
t2 -------->1588078005.4082909
t2 -------->1588078010.4268317
t2 -------->1588078015.4283602Con la tecnología de
Implementar threading con el módulo threading
Use mos el mismo ejemplo de antes, pero esta vez con el módulo threading en lugar de _thread.
t1 -------->1588081121.7790291
t1 -------->1588081122.7962046
t2 -------->1588081123.7789369
t1 -------->1588081123.7980876
t2 -------->1588081126.7961557
t2 -------->1588081129.7995002Con la tecnología de
También puedes usar el método join, que hace que el programa espere hasta que acaben todos los hilos. Cualquier código después de join se ejecutará solo cuando estos hilos hayan terminado.
t1 = threading.Thread(target=volume_cube, args=(2))
t2 = threading.Thread(target=volume_square, args=(3))Con la tecnología de
t1.start()
t2.start()
t1.join()
t2.join()print("Thread execution is complete!")Con la tecnología de
Thread execution is complete!
Exception in thread Thread-9:Traceback(most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926,in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870,in run
self._target(*self._args,**self._kwargs)TypeError:volume_square() argument after * must be an iterable, not int
Exception in thread Thread-8:Traceback(most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926,in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870,in run
self._target(*self._args,**self._kwargs)TypeError:volume_cube() argument after * must be an iterable, not int
Con la tecnología de
¿Cómo? En cuanto ejecutaste start apareció un error. Es porque el parámetro args espera una tupla, y como volume_cube y volume_square solo esperan un parámetro, necesitas poner una coma en args tras indicar el primer argumento de las funciones.
t1 = threading.Thread(target=volume_cube, args=(2,))
t2 = threading.Thread(target=volume_square, args=(3,))Con la tecnología de
t1.start()
t2.start()
t1.join()
t2.join()print("Thread execution is complete!")Con la tecnología de
Volume ofCube:8
Volume ofSquare:9
Thread execution is complete!Con la tecnología de
Veamos ahora cómo usar threading creando una subclase.
Threading como subclase
En esta sección aprenderás a crear una subclase de la clase Thread, disponible en el módulo threading, y luego crear instancias de hilo t1 y t2 a partir de esa clase.
Usaremos de nuevo la función thread_delay.
classDataCampThread(threading.Thread):def__init__(self, name, delay):
threading.Thread.__init__(self)
self.name = name
self.delay = delay
defrun(self):print('Starting Thread:', self.name)
thread_delay(self.name,self.delay)print('Execution of Thread:', self.name,'is complete!')
t1 = DataCampThread('t1',1)
t2 = DataCampThread('t2',3)
t1.start()
t2.start()
t1.join()
t2.join()print("Thread execution is complete!")Con la tecnología de
Starting Thread: t1
Starting Thread: t2
t1 -------->1588089083.4879806
t1 -------->1588089084.489222
t1 -------->1588089085.4909666
Execution ofThread: t1 is complete!
t2 -------->1588089085.4979634
t2 -------->1588089088.499365
t2 -------->1588089091.5012596
Execution ofThread: t2 is complete!
Thread execution is complete!Con la tecnología de
Entendamos el código paso a paso:
Definiste una clase DataCampThread y, como argumento de clase, pasaste threading.Thread. Lo haces para crear una subclase del módulo threading. Así puedes usar todos los métodos disponibles en threading.Thread, de forma similar a la herencia.
Después definiste el método init de DataCampThread y pasaste el nombre y el retardo. Este init actúa como constructor, de forma análoga a los parámetros que pasaste a threading.Thread, pero aquí para la clase DataCampThread.
Luego inicializas el init de la clase base threading.Thread y asignas name y delay.
Por último, defines el método run. Recuerda que aquí usaste el método start, que bajo el capó llama a run. Como has heredado de la clase principal, puedes modificar run. Así, cuando llames a start, se reflejarán los cambios que hagas en run.
Conclusión
¡Enhorabuena por completar el tutorial!
Este tutorial ha sido una introducción básica al threading en Python. Sin embargo, es un tema amplio y esencial, y puede que hayan quedado conceptos por explorar. No dudes en investigar otros métodos como active count, current thread, etc.
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.
Un tutorial introductorio completo sobre los bucles en Python. ¡Aprende y practica los bucles while y Bucle for, los bucles anidados, las palabras clave break y continue, la función range y mucho más!
Esta guía completa te enseña los fundamentos del backend con Python. Aprende conceptos básicos, frameworks y buenas prácticas para empezar a crear aplicaciones web.
Descubre cómo escribir funciones en Python reutilizables y eficientes. Domina los parámetros, las sentencias return y temas avanzados como las funciones lambda. Organiza mejor tu código con main() y otras buenas prácticas.
Aprende a crear e importar módulos de Python. ¡Descubre las mejores prácticas, ejemplos y consejos para escribir código Python reutilizable, organizado y eficiente!
Si acabas de empezar con Python y quieres seguir aprendiendo, haz el curso Introduction to Data Science in Python de DataCamp.
Introducción
El threading permite que distintas partes de tu proceso se ejecuten de forma concurrente (Fuente: RealPython). Estas partes suelen ser independientes y tienen una unidad de ejecución separada que pertenece al mismo proceso. Un proceso no es más que un programa en ejecución que contiene unidades que pueden ejecutarse en paralelo. Por ejemplo, un navegador web puede ser un proceso; una aplicación que usa varias cámaras a la vez puede ser otro proceso; un videojuego es otro ejemplo de proceso.
Dentro de un proceso aparece el concepto de múltiples hilos, o multithreading, donde varios hilos colaboran para alcanzar un objetivo común. La gran ventaja de usar hilos es que te permite ejecutar el programa en paralelo.
Veamos el concepto de threading con un ejemplo. Imagina una aplicación que cuenta los coches que entran y salen del aparcamiento de un centro comercial. El sistema tiene varias cámaras conectadas a un dispositivo central que monitorizan entradas y salidas. Cada cámara ejecuta un algoritmo para vigilar el flujo de coches y forma parte del mismo proceso o programa. Sin embargo, cada cámara, junto con el algoritmo que ejecuta, podría estar en un hilo independiente. Es más, la lectura de fotogramas de la cámara y la inferencia del algoritmo sobre esos fotogramas podrían ser dos hilos distintos.
Otro ejemplo sería un videojuego en el que el proceso necesita ejecutar tareas en paralelo como los gráficos, la interacción con el usuario y la red (en partidas multijugador) para ser siempre responsivo. Para lograrlo, recurre al multithreading, asignando a cada hilo la responsabilidad de una tarea independiente.
Un hilo tiene su propio flujo de ejecución, lo que significa que en el proceso suceden varias cosas a la vez.
Ten en cuenta que todo proceso tiene al menos un hilo, llamado
main thread(hilo principal). Si tu programa no define ningún hilo adicional, al menos contará con el hilo principal, es decir, el propio proceso.Si observas el diagrama superior, hay un proceso con el hilo principal que contiene variables globales y tres hilos diferentes t1, t2 y t3. Cada hilo tiene sus variables locales y su flujo de ejecución. Los hilos comparten en general los recursos del proceso; es decir, los recursos disponibles para el hilo principal también se comparten con los hilos t1, t2 y t3. Todos los hilos pueden acceder a las variables globales y, además, cuentan con sus variables locales.
Existen distintos tipos de hilos:
Veamos ahora algunas ventajas de usar threading en tu programa:
El multithreading puede acelerar la ejecución si dispones de múltiples CPU.
También te permite realizar otras tareas mientras se ejecutan operaciones de E/S (I/O) gracias a varios hilos o incluso con el hilo principal más un hilo. Por ejemplo, la lectura de fotogramas de la cámara y la inferencia del algoritmo se gestionarán en hilos diferentes. Así, el algoritmo no tendrá que esperar a que llegue un fotograma y la lectura de fotogramas no tendrá que esperar a que termine la ejecución del algoritmo para leer el siguiente.
Retos del threading
Recuerda que Python, en su implementación CPython, limita la ejecución a un solo hilo a la vez; por tanto, threading no acelera todas las tareas. La razón principal es el Global Interpreter Lock (GIL).
Si quieres aprender más sobre el GIL, echa un vistazo a este tutorial.
Si buscas acelerar tareas intensivas de CPU, threading puede no ser la mejor solución. En esos casos, suele ser más útil el multiprocesamiento.
Para ejecutar un programa con muchos hilos, es necesario cambiar de uno a otro con rapidez; por eso, la planificación (scheduling) es clave en programas con muchos hilos.
El uso compartido de recursos también puede ser problemático, ya que todos los hilos comparten la misma memoria y las variables globales. Una operación en un hilo podría provocar un error de memoria en otro, o impedir que otro hilo obtenga memoria para realizar su tarea.
Threading en Python
En Python, el módulo de hilos es un módulo integrado llamado
threadingy puede importarse directamente.Como casi todo en Python se representa como objeto, los hilos también son objetos. Un hilo es capaz de:
Un hilo también puede ejecutarse como un proceso.
Un hilo en Python puede estar en varios estados, como:
Veamos ahora cómo implementar threading en Python.
Módulo thread en Python 3
Ten en cuenta que Python 3 mantiene compatibilidad con el módulo
threadde Python 2.7. En Python 3 puede importarse como módulo_thread. Veamos un ejemplo para entender el módulo_thread.Definamos una función llamada
thread_delayque recibe dos parámetros: el nombre del hilo y el retardo. Dentro de esta función:time.sleep()para añadir un retardo; esto te ayudará a ver cómo se ejecuta el hilo. El retardo se expresa en segundos,Ahora, para ejecutar la función anterior en un hilo, usa el método
start_new_thread, que pertenece al módulo_thread.Veamos la docstring del método
start_new_thread.Pasa la función
thread_delaysin paréntesis y dos argumentos: el nombre del hilo y el retardo (para poder visualizar la ejecución, ya que es muy rápida).En la salida puedes ver que el hilo
t1empieza a ejecutarse primero. Mientras tanto,t2espera por su retardo de 3 segundos y, en cuanto termina, se ejecuta. El retardo det1es de solo 1 segundo.Cambiemos ahora el retardo de
t2a 5 segundos, de modo quet2empiece cuandot1ya haya terminado, ya quet1tardará solo 3 segundos en completarse.Implementar threading con el módulo threading
Use mos el mismo ejemplo de antes, pero esta vez con el módulo
threadingen lugar de_thread.Dentro del módulo
threadingestá la claseThread, similar en espíritu a la funciónstart_new_threaddel módulo_thread.Consultemos la docstring de la clase
Thread, que admite varios parámetros como group, target (una función), args, etc.En el constructor de Thread, pasarás la función objetivo
thread_delayy los argumentos de esa función.En el módulo threading, para ejecutar el hilo utilizas el método
start(), encargado de ponerlo en marcha.También puedes usar el método
join, que hace que el programa espere hasta que acaben todos los hilos. Cualquier código después dejoinse ejecutará solo cuando estos hilos hayan terminado.Calculemos ahora el volumen de un cubo y de un cuadrado usando threading; es bastante directo.
¿Cómo? En cuanto ejecutaste
startapareció un error. Es porque el parámetroargsespera una tupla, y comovolume_cubeyvolume_squaresolo esperan un parámetro, necesitas poner una coma enargstras indicar el primer argumento de las funciones.Veamos ahora cómo usar threading creando una subclase.
Threading como subclase
En esta sección aprenderás a crear una subclase de la clase Thread, disponible en el módulo threading, y luego crear instancias de hilo
t1yt2a partir de esa clase.Usaremos de nuevo la función
thread_delay.Entendamos el código paso a paso:
Definiste una clase
DataCampThready, como argumento de clase, pasastethreading.Thread. Lo haces para crear una subclase del módulo threading. Así puedes usar todos los métodos disponibles enthreading.Thread, de forma similar a la herencia.Después definiste el método init de
DataCampThready pasaste el nombre y el retardo. Este init actúa como constructor, de forma análoga a los parámetros que pasaste athreading.Thread, pero aquí para la claseDataCampThread.Luego inicializas el init de la clase base
threading.Thready asignas name y delay.Por último, defines el método
run. Recuerda que aquí usaste el métodostart, que bajo el capó llama arun. Como has heredado de la clase principal, puedes modificarrun. Así, cuando llames astart, se reflejarán los cambios que hagas enrun.Conclusión
¡Enhorabuena por completar el tutorial!
Este tutorial ha sido una introducción básica al threading en Python. Sin embargo, es un tema amplio y esencial, y puede que hayan quedado conceptos por explorar. No dudes en investigar otros métodos como active count, current thread, etc.
Si quieres profundizar, consulta la documentación de Python sobre paralelismo basado en hilos.
Si estás empezando con Python y quieres aprender más, haz el curso Introduction to Data Science in Python de DataCamp.