CodeGym /Cursos /Python SELF ES /Módulo threading

Módulo threading

Python SELF ES
Nivel 25 , Lección 1
Disponible

2.1 Módulo threading

La multitarea en Python es una forma de ejecutar varios hilos (threads) simultáneamente, lo que permite utilizar los recursos del procesador de manera más eficiente, especialmente para operaciones de entrada-salida u otras tareas que se pueden realizar en paralelo.

Conceptos básicos de la multitarea en Python:

Hilo — es la unidad más pequeña de ejecución, que puede funcionar en paralelo con otros hilos dentro de un mismo proceso. Todos los hilos en un proceso comparten la memoria común, lo que permite intercambiar datos entre hilos.

Proceso — es una instancia de un programa que se ejecuta en el sistema operativo, con su propio espacio de direcciones y recursos. A diferencia de los hilos, los procesos están aislados entre sí y se comunican a través de la interacción entre procesos (IPC).

GIL — es un mecanismo en el intérprete de Python que impide la ejecución simultánea de varios hilos de Python. GIL garantiza la seguridad de la ejecución del código de Python, pero limita el rendimiento de los programas multihilo en procesadores multinúcleo.

¡Importante! Ten en cuenta que debido al Global Interpreter Lock (GIL) la multitarea en Python puede no proporcionar un aumento significativo en el rendimiento para tareas intensivas en cálculos, ya que GIL impide la ejecución simultánea de varios hilos de Python en procesadores multinúcleo.

Módulo threading

El módulo threading en Python proporciona una interfaz de alto nivel para trabajar con hilos. Permite crear y manejar hilos, sincronizarlos y organizar la interacción entre ellos. Vamos a ver con más detalle los componentes y funciones clave de este módulo.

Componentes principales del módulo threading

Entidades para trabajar con hilos:

  • Thread — clase principal para crear y manejar hilos.
  • Timer — temporizador para ejecutar una función después de un tiempo determinado.
  • ThreadLocal — permite crear datos locales para el hilo.

Mecanismo de sincronización de hilos:

  • Lock — primitivo de sincronización para evitar el acceso simultáneo a recursos compartidos.
  • Condition — variable condicional para una sincronización de hilos más compleja.
  • Event — primitivo para notificaciones entre hilos.
  • Semaphore — primitivo para limitar el número de hilos que pueden ejecutar simultáneamente una sección determinada.
  • Barrier — sincroniza un número determinado de hilos, bloqueándolos hasta que todos lleguen a la barrera.

A continuación, hablaré sobre 3 clases para trabajar con hilos, el mecanismo de sincronización de hilos no lo necesitarás en un futuro cercano.

2.2 Clase Thread

La clase Thread — es la clase principal para crear y manejar hilos. Tiene 4 métodos principales:

  • start(): Inicia la ejecución del hilo.
  • join(): El hilo actual se suspende y espera a que finalice el hilo iniciado.
  • is_alive(): Devuelve True si el hilo está en ejecución.
  • run(): Método que contiene el código que se ejecutará en el hilo. Se sobrescribe al heredar de la clase Thread.

Todo es mucho más sencillo de lo que parece — ejemplo de uso de la clase Thread.

Inicio de un hilo simple


import threading

def worker():
    print("El hilo de trabajo está en ejecución")
            
# Creación de un nuevo hilo
t = threading.Thread(target=worker) #creamos un nuevo objeto de hilo
t.start() #Iniciamos el hilo
t.join() # Esperamos a que el hilo termine
print("El hilo principal ha terminado")
        

Después de llamar al método start, la función worker comenzará a ejecutarse. O, más precisamente, su hilo se añadirá a la lista de hilos activos.

Uso de argumentos


import threading

def worker(number, text):
    print(f"Trabajador {number}: {text}")
            
# Creación de un nuevo hilo con argumentos
t = threading.Thread(target=worker, args=(1, "Hola"))
t.start()
t.join()
        

Para pasar parámetros a un nuevo hilo, simplemente necesitas especificarlos como una tupla y asignarlos al parámetro args. Al llamar a la función, que fue indicada en target, los parámetros serán pasados automáticamente.

Sobrescribir el método run


import threading

class MyThread(threading.Thread):
    def run(self):
        print("El hilo personalizado está en ejecución")
            
# Creación e inicio del hilo
t = MyThread()
t.start()
t.join()
        

Hay dos formas de indicar la función con la que se debe comenzar la ejecución del nuevo hilo — se puede pasar a través del parámetro target al crear el objeto Thread, o heredar de la clase Thread y sobrescribir la función run. Ambas formas son legales y se utilizan constantemente.

2.3 Clase Timer

La clase Timer en el módulo threading está diseñada para iniciar una función después de un cierto intervalo de tiempo. Esta clase es útil para realizar tareas pospuestas en un entorno multihilo.

El temporizador se crea e inicializa con la función que se debe llamar y el tiempo de retardo en segundos.

  • Método start() inicia el temporizador, que cuenta el intervalo de tiempo especificado, y luego llama a la función indicada.
  • Método cancel() permite detener el temporizador si aún no ha funcionado. Esto es útil para evitar la ejecución de la función si el temporizador ya no es necesario.

Ejemplos de uso:

Ejecutar una función con retraso

En este ejemplo, la función hello se llamará 5 segundos después de iniciar el temporizador.


import threading

def hello():
    print("¡Hola, mundo!")
            
# Creación de un temporizador que llamará a la función hello en 5 segundos
t = threading.Timer(5.0, hello)
t.start()  # Inicio del temporizador
        

Detener el temporizador antes de la ejecución

Aquí el temporizador se detendrá antes de que la función hello tenga la oportunidad de ejecutarse, y, por lo tanto, no se imprimirá nada.


import threading

def hello():
    print("¡Hola, mundo!")
            
# Creación del temporizador
t = threading.Timer(5.0, hello)
t.start()  # Inicio del temporizador
            
# Detener el temporizador antes de la ejecución
t.cancel()
        

Temporizador con argumentos

En este ejemplo, el temporizador llamará a la función greet después de 3 segundos y le pasará el argumento "Alice".


import threading

def greet(name):
    print(f"¡Hola, {name}!")
            
# Creación de un temporizador con argumentos
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
        

La clase Timer es útil para planificar la ejecución de tareas después de un cierto tiempo. Sin embargo, los temporizadores no garantizan un tiempo de ejecución absolutamente preciso, ya que depende de la carga del sistema y del trabajo del planificador de hilos.

2.4 Clase ThreadLocal

La clase ThreadLocal está diseñada para crear hilos que tienen sus propios datos locales. Esto es útil en aplicaciones multihilo cuando cada hilo debe tener su propia versión de los datos para evitar conflictos y problemas de sincronización.

Cada hilo que usa ThreadLocal tendrá sus propias copias únicas de los datos. Los datos almacenados en un objeto ThreadLocal son únicos para cada hilo y no se comparten con otros hilos. Esto es conveniente para almacenar datos que se usan solo en el contexto de un hilo, como el usuario actual en una aplicación web o la conexión actual a la base de datos.

Ejemplos de uso:

Uso básico

En este ejemplo, cada hilo asigna su nombre a la variable local value e imprime su valor. El valor de value es único para cada hilo.


import threading

# Creación del objeto ThreadLocal
local_data = threading.local()
            
def process_data():
    # Asignación del valor a la variable local del hilo
    local_data.value = threading.current_thread().name
    # Acceso a la variable local del hilo
    print(f'Valor en {threading.current_thread().name}: {local_data.value}')

threads = []
for i in range(5):
    t = threading.Thread(target=process_data)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Almacenar datos del usuario en una aplicación web

En este ejemplo, cada hilo procesa una solicitud para su usuario. El valor de user_data.user es único para cada hilo.


import threading
# Creación del objeto ThreadLocal
user_data = threading.local()
def process_request(user):
    # Asignación del valor a la variable local del hilo
    user_data.user = user
    handle_request()

def handle_request():
    # Acceso a la variable local del hilo
    print(f'Manejando la solicitud para el usuario: {user_data.user}')

threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
    t = threading.Thread(target=process_request, args=(user,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Estos fueron los 3 clases más útiles de la biblioteca threading. Probablemente los usarás en tu trabajo, mientras que el resto de las clases - probablemente no. Ahora todos están pasando a funciones asíncronas y la biblioteca asyncio. De eso es lo que vamos a hablar en el futuro cercano.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION