HilosPython1.5#

Hilos (Threads) en Python#

Permiten aprovechar las capacidades multiprocesador para ejecutar varias instrucciones a la vez, como subprocesos independientes.

Chapter 2: Thread-based Parallelism

https://github.com/jsdnhk/python-parallel-programming-cookbook-code/tree/master/Chapter 2

¿Cómo utilizar los hilos?#

La forma más sencilla de usar un subproceso es instanciarlo con una función de destino y luego llamar al método start() para que comience a funcionar.

El subprocesamiento del módulo de Python tiene el método Thread():

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})

  • group: Este es el valor del grupo que debe ser Ninguno; esto está reservado para futuras implementaciones

  • target: esta es la función que se ejecutará cuando inicie una actividad de subproceso

  • name: Este es el nombre del hilo; por defecto, se le asigna un nombre único de la forma Thread-N

  • args: esta es la tupla de argumentos que se pasarán a un destino

  • kwargs: este es el diccionario de argumentos de palabras clave que se utilizarán para la función de destino

import threading        # Manejo de hilos
from time import sleep  # Pausar tiempo
import random           # Números aleatorios

def function(i):
    sleep(1.5*random.random()) #Tiempo de 0<t<1.5 
    return print ("Función llamada por el hilo %i" %i)

for i in range(5):
    t = threading.Thread(target=function, args=(i,)) # Instanciarlo con una función de destino
    t.start() # Comience a funcionar
    #t.join()  # Esperar por otro hilo
Función llamada por el hilo 0
Función llamada por el hilo 1
Función llamada por el hilo 2
Función llamada por el hilo 3
Función llamada por el hilo 4

¿Cómo determinar el hilo actual?#

  • Cada instancia de Thread asigna un nombre de forma predetermianda.

  • El uso de argumentos para identificar o nombrar el subproceso es innecesario.

  • Nombrar subprocesos es útil en procesos de servidor con múltiples subprocesos de servicio que manejan diferentes operaciones

import threading # Manejo de hilos
import time      # librería para tiempo

def first_function():
    print (threading.currentThread().getName()+str(' iniciando...\n'))
    time.sleep(1)
    print (threading.currentThread().getName()+str(' finalizó\n'))
    return

def second_function():
    print (threading.currentThread().getName()+str(' iniciando...\n'))
    time.sleep(5)
    print (threading.currentThread().getName()+str(' finalizó \n'))
    return


t1 = threading.Thread(name='Thread-Nombre', target=first_function) # Nombre de hilo asignado
t2 = threading.Thread(                      target=second_function) # Toma nombre de hilo por defecto automáticamente
t1.start()
t2.start()
    
Thread-Nombre iniciando...

Thread-21 iniciando...

Thread-Nombre finalizó

Thread-21 finalizó 

¿Cómo crear un hilo con temporizador de inicio?#

# Crear y ejecutar hilos con temporizador
import threading

def ejecutar(tiempo_s):
    print(f'El hilo {threading.current_thread().name} te saluda luego de tu espera de {tiempo_s} segundos')

# creamos un temporizador
tiempo_s = 5
temporizador = threading.Timer(tiempo_s, function=ejecutar, args=(tiempo_s,))  # Crear el hilo con temporizador
temporizador.start()                                   # El hilo empezará cuando pasen segundos dados
print("No te vayas, espera...")
No te vayas, espera...
El hilo Thread-23 te saluda luego de tu espera de 5 segundos

¿Cómo crear hilos, ejecutarlos y que el principal espere?#

import threading        # Manejo de hilos
from time import sleep  # Pausar tiempos
import random           # Números aleatorios

def ejecutar():
    print(f'Comienza {threading.current_thread().name}')
    sleep(1.5*random.random()) #Tiempo de 0<t<1.5 
    print(f'Termina {threading.current_thread().name}')

# Crear los hilos
hilo1 = threading.Thread(target=ejecutar, name='Hilo 1')
hilo2 = threading.Thread(target=ejecutar, name='Hilo 2')
hilo3 = threading.Thread(target=ejecutar, name='Hilo 3')

# Ejecutar los hilos
hilo1.start()
hilo2.start()
hilo3.start()

# Esperar a que terminen los hilos ejecutados
hilo1.join()
hilo2.join()
hilo3.join()

print('El hilo principal sí espera por el resto de hilos.')
Comienza Hilo 1
Comienza Hilo 2
Comienza Hilo 3
Termina Hilo 3
Termina Hilo 1
Termina Hilo 2
El hilo principal sí espera por el resto de hilos.

¿Cómo crear hilos, ejecutarlos y que el principal no espere?#

import threading        # Manejo de hilos
from time import sleep  # Pausar tiempos
import random           # Números aleatorios

def ejecutar():
    print(f'Comienza {threading.current_thread().name}')
    sleep(1.5*random.random()) #Tiempo de 0<t<1.5 
    print(f'Termina {threading.current_thread().name}')

# Crear los hilos
hilo1 = threading.Thread(target=ejecutar, name='Hilo 1')
hilo2 = threading.Thread(target=ejecutar, name='Hilo 2')
hilo3 = threading.Thread(target=ejecutar, name='Hilo 3')

# Ejecutar los hilos
hilo1.start()
hilo2.start()
hilo3.start()

# Esperar a que terminen los hilos ejecutados
#hilo1.join()
#hilo2.join()
#hilo3.join()

print('\nEl hilo principal no espera por el resto de hilos.\n')
Comienza Hilo 1
Comienza Hilo 2
Comienza Hilo 3

El hilo principal no espera por el resto de hilos.

Termina Hilo 2
Termina Hilo 1
Termina Hilo 3

¿Cómo implemento un hilo en una subclase?#

  • Definir una nueva subclase de la clase Thread

  • Llamar al constructor de la clase Thread es obligatorio; usándolo, podemos redefinir algunas propiedades del hilo como el nombre del hilo.

  • Sobrecargue el método init_(self [,args]) para agregar argumentos adicionales

  • Luego, debo sobrecargar el método run(self [,args]) para implementar lo que debe hacer el subproceso cuando se inicia

  • En el programa principal, creamos varios objetos del tipo myThread; la ejecución del hilo comienza cuando se llama al método start().

  • El subproceso se coloca en el estado activo de la llamada a start() y permanece allí hasta que finaliza el método run() o lanza una excepción no controlada.

  • El programa finaliza cuando todos los subprocesos finalizan.

Caso 1#

import threading
import time

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
    def run(self):
        print ("Iniciando " + self.name)
        print_time(self.name, 5,self.counter)  
        print ("Finalizando " + self.name)
        
def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print ("%s: %s\n" %(threadName, time.strftime("%H:%M:%S", time.localtime(time.time())))) # Thread-#: HH:MM:SS
        counter -= 1

# Crear hilos
thread1 = myThread(1, "Thread-1", 1) # Objetos del tipo myThread # Hilo que muestra la hora cada 1 segundo
thread2 = myThread(2, "Thread-2", 4) # Objetos del tipo myThread # Hilo que muestra la hora cada 4 segundos

# Iniciar los hilos
thread1.start() #la ejecución del hilo comienza cuando se llama al método start(). 
thread2.start() #la ejecución del hilo comienza cuando se llama al método start(). 
Iniciando Thread-1
Iniciando Thread-2
Thread-1: 08:29:38

Finalizando Thread-1
Thread-2: 08:29:38

Thread-2: 08:29:43

Thread-2: 08:29:48

Thread-2: 08:29:53

Finalizando Thread-2

Caso 2#

import threading
import time

class myThread (threading.Thread):
    def __init__(self, threadID, name, delay):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.delay = delay
    def run(self):
        print ("Iniciando " + self.name)
        print_time(self.name, self.delay,5)  
        print ("Finalizando " + self.name)
        
def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print ("%s: %s\n" %(threadName, time.strftime("%H:%M:%S", time.localtime(time.time())))) # Thread-#: HH:MM:SS
        counter -= 1

# Crear hilos
thread1 = myThread(1, "Thread-1", 1) # Objetos del tipo myThread # Hilo que muestra la hora cada 1 segundo
thread2 = myThread(2, "Thread-2", 3) # Objetos del tipo myThread # Hilo que muestra la hora cada 4 segundos

# Iniciar los hilos
thread1.start() #la ejecución del hilo comienza cuando se llama al método start(). 
thread2.start() #la ejecución del hilo comienza cuando se llama al método start(). 
Iniciando Thread-1
Iniciando Thread-2
Thread-1: 08:32:16

Thread-1: 08:32:17

Thread-2: 08:32:18

Thread-1: 08:32:18

Thread-1: 08:32:19

Thread-1: 08:32:20

Finalizando Thread-1
Thread-2: 08:32:21

Thread-2: 08:32:24

Thread-2: 08:32:27

Thread-2: 08:32:30

Finalizando Thread-2

¿Hay que tomar precauciones al utilizar los hilos?#

  • Mientras el programa principal ha llegado al final, el hilo continúa imprimiendo su mensaje cada dos segundos.

  • Este ejemplo demuestra qué son los subprocesos: una subtarea que hace algo en un proceso principal.

Un punto clave a tener en cuenta al usar subprocesos es que siempre debe asegurarse de nunca dejar ningún subproceso ejecutándose en segundo plano. Esta es una programación muy mala y puede causarle todo tipo de problemas cuando trabaja en aplicaciones más grandes.

from threading import Thread   
from time import sleep        

class CookBook(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.message = "Hilo Trabajando!\n"
         
    def print_message(self):
        print (self.message)
         
    def run(self):
        print ("Hilo Inicia\n")
        x=0
        while (x < 3):
            self.print_message()
            sleep(2)  
            x += 1
        print ("Hilo Finaliza\n")    

print('\nPrograma principal inicia.\n')
hello_Python = CookBook()
hello_Python.start()
print('\nPrograma principal finaliza.\n')
Programa principal inicia.

Hilo Inicia

Hilo Trabajando!


Programa principal finaliza.

Hilo Trabajando!

Hilo Trabajando!

Hilo Finaliza

¿Cómo hago para detener a los hilos?#

Iniciar mis hilos#

import threading
import time
import numpy as np

# Variable global para indicar a los hilos que deben detenerse
global exit_flag
exit_flag = False

# Función que ejecutará cada hilo
def thread_function(thread_num):    
    while not exit_flag:
        
        # Es aquí donde puedes poner el código que hace el trabajo del hilo
        print(f"Hola, soy el hilo {thread_num}")
        time.sleep(2*np.random.rand())  
        
        pass
    print(f"Hilo {thread_num} detenido")

# Crear los hilos
threads = []
for i in range(2):
    t = threading.Thread(target=thread_function, args=(i,))
    threads.append(t)

# Iniciar los hilos
for t in threads:
    t.start()
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 0
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 0
Hola, soy el hilo 1
Hola, soy el hilo 1
Hola, soy el hilo 0
Hola, soy el hilo 1

Detener mis hilos#

# Detener todos los hilos
exit_flag = True
Hilo 1 detenido
Hilo 0 detenido

¿Sumar matrices por filas? Veamoslo#

Realizaré la suma de las filas de tres matrices, cada una con cuatro filas y diez columnas. Tengo a mi disposición cuatro hilos de procesamiento y utilizaré un hilo para procesar cada conjunto de filas de las tres matrices. El proceso de suma de filas tarda aproximadamente 2 segundos en ejecutarse.

¿Hilo por hilo? 😒#

# Librerias que utilizaremos
import threading   # Manejo de hilos
import numpy as np # Manejo de matrices
import time        # Manejo de tiempos

# Definir las 3 matrices de 4x10, con números enteros entre 0 y 10
matrix1 = np.round(10*np.random.rand(4, 10)).astype(np.int8)
matrix2 = np.round(10*np.random.rand(4, 10)).astype(np.int8)
matrix3 = np.round(10*np.random.rand(4, 10)).astype(np.int8)

# Definir una matriz resultado de 4x10 con todos los valores en cero
result_matrix = np.zeros((4, 10)).astype(np.int8)

# Definir una función para sumar las filas de las matrices
def sum_rows(row_idx):
    global result_matrix
    result_matrix[row_idx] = matrix1[row_idx] + matrix2[row_idx] + matrix3[row_idx]
    time.sleep(2) # Se demora 2 segundos sumando una fila

# Iniciar medición del tiempo
inicio = time.time()    
    
# Crear 4 hilos, uno para cada fila
hilo1 = threading.Thread(target=sum_rows, args=(0,)) # Suma fila 1
hilo2 = threading.Thread(target=sum_rows, args=(1,)) # Suma fila 2
hilo3 = threading.Thread(target=sum_rows, args=(2,)) # Suma fila 3
hilo4 = threading.Thread(target=sum_rows, args=(3,)) # Suma fila 4

hilo1.start() # Poner a trabajar hilo 1
hilo1.join()  # Esperar a que el hilo 1 termine

hilo2.start() # Poner a trabajar hilo 2
hilo2.join()  # Esperar a que el hilo 2 termine

hilo3.start() # Poner a trabajar hilo 3
hilo3.join()  # Esperar a que el hilo 3 termine

hilo4.start() # Poner a trabajar hilo 4
hilo4.join()  # Esperar a que el hilo 4 termine

# Imprimir las matrices y la matriz resultado
print("Matrix1:\n", matrix1)
print("Matrix2:\n", matrix2)
print("Matrix3:\n", matrix3)
print("Result Matrix:\n", result_matrix)

# Mostrar tiempo requerido
fin = time.time()
tiempo_total = fin - inicio
print(tiempo_total,"segundos")
Matrix1:
 [[3 7 2 4 7 8 8 4 4 5]
 [7 0 7 7 4 5 6 2 1 8]
 [3 3 3 3 3 1 7 5 6 4]
 [8 3 8 1 0 9 6 0 1 9]]
Matrix2:
 [[ 2  1  5  2 10  6  1  5  4  2]
 [ 4  0  5  5  5  9  6  3  6  8]
 [ 5 10  6  7  7  4  1  8  6  3]
 [ 6  8  3  1  3  9  8  0  7  6]]
Matrix3:
 [[ 0  2  3  9  6 10  6 10 10  5]
 [10  0  4  8  7 10  7  4  2  8]
 [ 3  0  6  6  9  5  5  9  5  0]
 [ 1  6  6  8  4  2  5  5  8  1]]
Result Matrix:
 [[ 5 10 10 15 23 24 15 19 18 12]
 [21  0 16 20 16 24 19  9  9 24]
 [11 13 15 16 19 10 13 22 17  7]
 [15 17 17 10  7 20 19  5 16 16]]
8.04371976852417 segundos

¿Todos los hilos al tiempo? 😃#

# Librerias que utilizaremos
import threading   # Manejo de hilos
import numpy as np # Manejo de matrices
import time        # Manejo de tiempos

# Definir las 3 matrices de 4x10, con números enteros entre 0 y 10
matrix1 = np.round(10*np.random.rand(4, 10)).astype(np.int8)
matrix2 = np.round(10*np.random.rand(4, 10)).astype(np.int8)
matrix3 = np.round(10*np.random.rand(4, 10)).astype(np.int8)

# Definir una matriz resultado de 4x10 con todos los valores en cero
result_matrix = np.zeros((4, 10)).astype(np.int8)

# Definir una función para sumar las filas de las matrices
def sum_rows(row_idx):
    global result_matrix
    result_matrix[row_idx] = matrix1[row_idx] + matrix2[row_idx] + matrix3[row_idx]
    time.sleep(2) # Se demora 2 segundos sumando una fila

# Iniciar medición del tiempo
inicio = time.time()    
    
# Crear 4 hilos, uno para cada fila
hilo1 = threading.Thread(target=sum_rows, args=(0,)) # Suma fila 1
hilo2 = threading.Thread(target=sum_rows, args=(1,)) # Suma fila 2
hilo3 = threading.Thread(target=sum_rows, args=(2,)) # Suma fila 3
hilo4 = threading.Thread(target=sum_rows, args=(3,)) # Suma fila 4

# Poner a trabajar a los hilos
hilo1.start()
hilo2.start()
hilo3.start()
hilo4.start()

# Esperar a que todos los hilos terminen
hilo1.join()
hilo2.join()
hilo3.join()
hilo4.join()

# Imprimir las matrices y la matriz resultado
print("Matrix1:\n", matrix1)
print("Matrix2:\n", matrix2)
print("Matrix3:\n", matrix3)
print("Result Matrix:\n", result_matrix)

# Mostrar tiempo requerido
fin = time.time()
tiempo_total = fin - inicio
print(tiempo_total,"segundos")
Matrix1:
 [[ 5  1  7  2  7  6  2  4  4  9]
 [ 3 10  1  0  3  7  2  9  8  9]
 [ 5  2  1  0  4  8  2  8  4  4]
 [ 9  6  4  9  1 10  4  1  3  0]]
Matrix2:
 [[ 5  4  3  5  3  9  7  2  0  0]
 [ 1  3  9  3 10  8  3  0  8  6]
 [ 7  3 10  4  3  8  0  8 10  6]
 [ 8  5  7  0 10  6 10  7  8 10]]
Matrix3:
 [[10  2  6  8  8  3  3  6  2  6]
 [ 6  2 10  1  4  1  0  7  9  0]
 [ 6  7  9  5  0  2  1  2  1  6]
 [ 5  7 10  9  4  6  2  9  1  4]]
Result Matrix:
 [[20  7 16 15 18 18 12 12  6 15]
 [10 15 20  4 17 16  5 16 25 15]
 [18 12 20  9  7 18  3 18 15 16]
 [22 18 21 18 15 22 16 17 12 14]]
2.02053165435791 segundos

¿Y si sólo tengo 2 hilos? 😵‍💫#

# Librerias que utilizaremos
import threading   # Manejo de hilos
import numpy as np # Manejo de matrices
import time        # Manejo de tiempos

# Definir las 3 matrices de 4x10, con números enteros entre 0 y 10
matrix1 = np.round(10*np.random.rand(4, 10)).astype(np.int8)
matrix2 = np.round(10*np.random.rand(4, 10)).astype(np.int8)
matrix3 = np.round(10*np.random.rand(4, 10)).astype(np.int8)

# Definir una matriz resultado de 4x10 con todos los valores en cero
result_matrix = np.zeros((4, 10)).astype(np.int8)

# Definir una función para sumar las filas de las matrices
def sum_rows(row_idx):
    global result_matrix
    result_matrix[row_idx] = matrix1[row_idx] + matrix2[row_idx] + matrix3[row_idx]
    time.sleep(2) # Se demora 2 segundos sumando una fila

# Iniciar medición del tiempo
inicio = time.time()    
    
# Crear 2 hilos, uno para cada fila
hilo1 = threading.Thread(target=sum_rows, args=(0,)) # Suma fila 1
hilo2 = threading.Thread(target=sum_rows, args=(1,)) # Suma fila 2
# Poner a trabajar a los hilos
hilo1.start()
hilo2.start()
# Esperar a que todos los hilos terminen
hilo1.join()
hilo2.join()

# Crear 2 hilos, uno para cada fila
hilo1 = threading.Thread(target=sum_rows, args=(2,)) # Suma fila 2
hilo2 = threading.Thread(target=sum_rows, args=(3,)) # Suma fila 3
# Poner a trabajar a los hilos
hilo1.start()
hilo2.start()
# Esperar a que todos los hilos terminen
hilo1.join()
hilo2.join()

# Imprimir las matrices y la matriz resultado
print("Matrix1:\n", matrix1)
print("Matrix2:\n", matrix2)
print("Matrix3:\n", matrix3)
print("Result Matrix:\n", result_matrix)

# Mostrar tiempo requerido
fin = time.time()
tiempo_total = fin - inicio
print(tiempo_total,"segundos")
Matrix1:
 [[ 0  4  3  2 10 10  3  2  1  6]
 [ 2  3 10  7  7  6  1  2  7  2]
 [ 2  9 10  5  4  7  3  5  4  8]
 [ 4  9 10  7  8  7  7  3  4  2]]
Matrix2:
 [[ 2  4  6  6  3  2  6  4  6  8]
 [ 1  4  4  3  3  9  7  9  3  0]
 [ 7  3  7  5  4  9  0  4  0  7]
 [10  1  4 10  8  6  2  4  7  1]]
Matrix3:
 [[ 4  7  6  6  2  6  5  4  3  6]
 [ 7 10  2  8  9  2  2 10  2  7]
 [ 3 10  0  5  0  0  9  4  1  3]
 [ 2  7  4  5  6  9  6  9  3  1]]
Result Matrix:
 [[ 6 15 15 14 15 18 14 10 10 20]
 [10 17 16 18 19 17 10 21 12  9]
 [12 22 17 15  8 16 12 13  5 18]
 [16 17 18 22 22 22 15 16 14  4]]
4.024723529815674 segundos