
Al programar en Python, a menudo te enfrentas a problemas al implementar interfaces gráficas como tkinter.
Al presionar un botón para ejecutar una función específica, la GUI también se congela.
Decidí resolver esto con multithreading.
1. ¿Multithreading? ¿Multiprocesamiento?

El multithreading es una técnica que permite ejecutar múltiples flujos de trabajo (hilos) simultáneamente dentro de un solo proceso.
Por ejemplo, cuando una GUI hecha con tkinter está corriendo en el hilo principal y una función ejecutada al presionar un botón tarda mucho, la GUI también se congela.
Programas de admisión como UNIV, conocidos por quienes trabajan en instituciones educativas, son un ejemplo de esto.

Al abrir una ventana específica, la ventana existente se congela, lo que resulta muy inconveniente para los usuarios que interactúan con la GUI.
En tales casos, al ejecutar funciones presionando un botón en un hilo separado, la GUI sigue funcionando en el hilo principal y se ejecuta la función en otro hilo, evitando que la pantalla se congele.

Por otro lado, el multiprocesamiento crea y ejecuta un proceso completamente diferente.
Los hilos se ejecutan compartiendo memoria dentro de un proceso, pero el multiprocesamiento ejecuta procesos independientes con memoria separada.
Aunque puede utilizar múltiples núcleos de CPU y es beneficioso para tareas que requieren gran cantidad de cálculos, no es tan sencillo de usar como el multithreading en programas GUI, ya que requiere comunicación entre procesos separados.
Categoría | Multithreading | Multiprocesamiento |
|---|---|---|
Método de Ejecución | Varios hilos en un solo proceso | Varios procesos independientes |
Memoria | Compartición de espacio de memoria | Espacio de memoria independiente (duplicado) |
Uso de CPU | Restringido en tareas CPU-bound debido a GIL (Global Interpreter Lock) | Puede usar múltiples núcleos de CPU |
Relación con GUI | Adecuado para prevenir congelación de GUI | Requiere comunicación al ser un proceso separado de GUI |
Ejemplo de Uso | Ejecución de función tras clic de botón en tkinter | Procesamiento de imágenes, cálculos a gran escala, etc. |
El multiprocesamiento es posible en Python porque la librería multiprocessing permite duplicar o crear nuevos procesos para ejecutarlos.
No obstante, la memoria es independiente por proceso, por lo que para compartir datos se deben usar Queue, Pipe, memoria compartida, etc.
En resumen, es aconsejable usar multithreading en programas GUI para que la interfaz no se congele y multiprocesamiento para trabajos computacionales pesados.
2. Ejemplo de código de multithreading
Supongamos que tenemos el siguiente código.
Este código imprime 'hello' y 'hi' tantas veces como el número proporcionado.
import time
def printHello(num):
for i in range(num):
print(f'hello-{i}')
time.sleep(1)
def printHi(num):
for i in range(num):
print(f'hi-{i}')
time.sleep(1)
printHello(3)
printHi(2)
Si ejecutas el código como en la última línea, se imprime 'Hello' tres veces y luego 'Hi' dos veces.
Puesto que Python ejecuta funciones de manera sincrónica, la siguiente función no se ejecuta hasta que se complete la anterior.
hello-0
hello-1
hello-2
hi-0
hi-1
A continuación, lo implementaremos con multithreading.
Las funciones printHello y printHi son las mismas y los comentarios proporcionan una explicación simple del código.
import threading
import time
...
hello = threading.Thread(target=printHello, args=(3, ), daemon=True)
hello.start()
hi = threading.Thread(target=printHi, args=(2, ), daemon=True)
hi.start()
hello.join()
hi.join()
print('Finalización del trabajo')
Al ejecutar esto, 'hello' y 'hi' se imprimen simultáneamente a intervalos de 1 segundo.
Y después de la última impresión de 'hello', se muestra 'Finalización del trabajo'.
hello-0hi-0
hello-1hi-1
hello-2
Finalización del trabajo
Estos son algunos de los parámetros clave de Thread.
Por lo general, es bueno establecer daemon en true para su bienestar mental.
Opción | Descripción | Ejemplo |
|---|---|---|
| Función que se ejecutará |
|
| Parámetros que se pasarán a la función (tupla) |
|
| Indicador de hilo daemon. Si es |
|
3. ¿Usar futures en lugar de threading?
Usar threading no es particularmente difícil, pero requiere procesos adicionales como daemon y join.
Python ha soportado la librería concurrent.futures desde la versión 3.2, y usarla permite realizar implementaciones más sencillas.
import time
from concurrent.futures import ThreadPoolExecutor
def printHello(num, name):
for i in range(num):
print(f'{name}-hello-{i}')
time.sleep(1)
def printHi(num, name):
for i in range(num):
print(f'{name}-hi-{i}')
time.sleep(1)
executor = ThreadPoolExecutor(max_workers=2)
future1 = executor.submit(printHello, 5, "Alice")
future2 = executor.submit(printHi, 5, "Bob")
future1.result()
future2.result()
print("Todas las tareas completadas")
Al usar submit, solo necesitas proporcionar la función y los parámetros de la función en el orden correcto.
Desde el punto en que se llama a submit, la ejecución de la función comienza, y al llamar a result, espera hasta que la función termine y puedes obtener el valor de retorno.
Si no llamas a result, el hilo principal no espera y el programa podría terminar antes.
No es necesario declarar daemon internamente ya que se maneja automáticamente.
Es más intuitivo que threading.
Para prevenir pérdidas de memoria, también puedes envolverlo con una sentencia with, como se muestra a continuación.
with ThreadPoolExecutor(max_workers=2) as executor:
future1 = executor.submit(printHello, 5, "Alice")
future2 = executor.submit(printHi, 5, "Bob")
future1.result()
future2.result()
La sentencia with llama automáticamente a executor.shutdown(wait=True) para cerrar los hilos de manera segura.
4. Ejemplo de ejecución simultánea de GUI y funciones
Ahora implementemos una simple GUI y funciones para ejecutarlas simultáneamente.
Usaremos customtkinter para la GUI.
Intentaremos usar las funciones printHello y printHi como lo hicimos anteriormente.
Implementé la clase como se muestra a continuación.
from concurrent.futures import ThreadPoolExecutor
import time
import customtkinter as ctk
class helloHiWindow:
def __init__(self):
self.hello = 5
self.hi = 6
self.app = ctk.CTk()
self.executor = ThreadPoolExecutor(max_workers=2)
self.customWindow()
def run(self):
self.app.mainloop()
def customWindow(self):
self.app.title("Hello! Hi!")
self.app.geometry("300x200")
button1 = ctk.CTkButton(self.app, text="Hello!", height=10, command=self.startHello)
button1.pack(pady=20)
button2 = ctk.CTkButton(self.app, text="Hi!", height=10, command=self.startHi)
button2.pack(pady=20)
def startHello(self):
hello = self.executor.submit(self.printHello)
def startHi(self):
hi = self.executor.submit(self.printHi)
def printHello(self):
for i in range(self.hello):
print(f'hello-{i}')
time.sleep(1)
def printHi(self):
for i in range(self.hi):
print(f'hi-{i}')
time.sleep(1)
if __name__ == "__main__":
app = helloHiWindow()
app.run()Si usas Mac, necesitarás instalar una librería con brew install python-tk.
Después de crear esto y ejecutar la ventana, la interfaz se verá como se muestra a continuación.
Los clics en los botones producirán la salida.

Incluso al hacer clic repetidamente, la ventana existente no se congela.
Esto se debe a que la ventana corre en el hilo principal mientras que las funciones de impresión se ejecutan en un hilo separado.
De esta manera, la ventana no se bloquea y las funciones pueden ejecutarse.
5. ¿No es esto realmente multithreading...?
Python tiene algo llamado GIL (Global Interpreter Lock) que impide que más de un hilo se ejecute simultáneamente en la CPU.
Esto fue creado para mejorar la velocidad y estabilidad del desarrollo en Python.
Sin embargo, el código anterior parece ejecutarse simultáneamente.

Esto se debe a que el código de Python cambia rápidamente de un hilo a otro.
Es similar al Event Loop en JavaScript.
Funciones como time.sleep(1) esperan sin ocupar la CPU, permitiendo a otras funciones ejecutarse en el intermedio.
Esto hace que un solo hilo parezca comportarse como varios.
6. Conclusión
Al principio, cuando aprendí Python, el concepto de multithreading (multitasking) parecía complicado y difícil.
Al principio solo usaba la terminal, pero a medida que desarrollaba más, se volvió confuso y decidí implementar una GUI con customtkinter.
Durante ese proceso, comprendí claramente el multithreading.
Se aprende más con proyectos de desarrollo que con conferencias o clases.
Voy a seguir trabajando en proyectos continuamente.
댓글을 불러오는 중...