Bienvenidos sean a este post, hoy hablaremos sobre que son los threads en python.
Un thread o hilo (tambien se lo conoce de esta forma) puede ser definido como una secuencia de instrucciones que puede ser ejecutado por un planificador, esa es la parte del sistema operativo que decide cual trozo del trabajo recibira los recursos necesarios para ser llevado a cabo, usualmente un thread vive dentro de un proceso y un proceso lo podemos definir como una instancia de un programa de computadora que esta siendo ejecutado.
En post anteriores hemos ejecutado algunos de nuestro codigos mediante el siguiente comando:
$ python3 mi_script.py
Pero que sucede cuando se ejecuta este comando? es creado un proceso de python? En realidad si pero dentro de este proceso se genero un thread principal y todas las instrucciones del script se ejecutaran dentro del thread, esta es solo una forma de trabajo pero python puede actualmente usar mas de un thread dentro del mismo proceso y puede incluso generar multiples procesos, como era de esperar a esto lo denomina multithreading y multiproceso respectivamente.
Anatomia de un thread
Los threads generalmente se dividen en dos partes:
- Nivel usuario, son aquellos que se pueden crear y manejar para ejecutar una tarea
- Nivel kernel, son de bajo nivel que corren en el modo kernel y actuan en nombre del sistema operativo
Como se pueden imaginar todo lo ejecutado por python son a nivel usuario, por el momento no nos sumergiremos en el nivel kernel, a continuacion enlistaremos algunos de los posibles estados de un thread:
- New, un thread que no ha sido iniciado todavia y no se ha asignado ningun recurso
- Runnable, el thread esta esperando para ejecutarse, tiene todos los recursos para ejecutar y tan pronto como el planificador de luz verde se ejecutara el mismo
- Running, un thread cuyo flujo de instrucciones esta siendo ejecutado, desde este estado se puede volver a un estado non-running o morir
- Not-running, un thread que ha sido pausado, esto puede ser debido a que otro thread tomo precedencia sobre este o simplemente porque el thread esta esperando para que finalice una operacion IO de larga duracion
- Dead, un thread que murio porque alcanzo su final natural del flujo de ejecucion o simplemente fue asesinado ☠
Las transiciones entre estado es provocado ya sea por una accion o un planificador pero hay una cosa que debemos mantener en mente: es mejor no interferir con la muerte de un thread.
Asesinando threads
Ejecutar esta accion no es una buena practica, dado que el lenguaje no provee una accion para eliminar un thread mediante una funcion o metodo y deberia ser mas que un indicio que realizar esta accion no es algo que quieras estar haciendo, la razon principal es que los threads suelen tener hijos, threads generados dentro del mismo thread, los cuales quedaran huerfanos cuando los padres mueren, suena rudo pero en programacion siempre se denomina de esta manera, y otra razon es que si el thread que estas terminando o cerrando, suena mejor que asesinar, contiene un recurso que debe cerrarse apropiadamente y nosotros evitamos que esto suceda puede derivar en problemas graves ya no solamente con un programa inestable sino hasta inestabilidad en el sistema operativo.
Nota: esto ultimo puede sonar exagerado pero no lo es ya que puede suceder.
Cambio de contexto
Hemos dicho que el planificador puede decidir cuando un thread puede ejecutar o ser pausado o sea el estado que necesita, en cualquier momento un estado necesita ser pausado asi otro proceso puede ejecutarse, el planificador salva el estado de un thread que se este ejecutando (Running) permitiendo de esta manera poder retomar la ejecucion exactamente donde se pauso, esta accion es llamado cambio de contexto.
Esto en realidad lo hace la gente todo el tiempo, por ejemplo estamos trabajando con algun papeleo y de repente escuchamos que el telefono llama, dejamos lo que estabamos haciendo y atendemos el telefono, una vez terminada la llamada retomamos el papeleo no desde el principio sino desde donde lo dejamos, como podemos ver esto es una habilidad maravillosa tanto de la vida diaria como de las computadoras pero esto puede derivar en un problema si generamos muchos threads, debido a que el planificador intentara darle a cada thread una posibilidad de correr por una muy pequeña cantidad de tiempo y tendremos una gran cantidad de perdida de tiempo en el salvado y recuperacion del estado de los threads que son pausados y reiniciados respectivamente.
Para evitar este problema es muy comun limitar la cantidad de threads, lo mismo aplica a los procesos, que pueden correrse en cualquier momento dado, esto se logra con una estructura llamada pool (piscina), el tamaño de este puede ser decidido por el programador, en otras palabras creamos un pool y le asignamos tareas para los threads, cuando todos los threads del pool estan ocupados hara que el programa no pueda generar nuevos threads hasta que uno de ellos termina y vuelve al pool, las pools tambien son geniales para salvar recursos y proporcionan caracteristicas de reciclaje en el ecosistema de los threads.
Cuando escribimos codigo multithread es util tener informacion sobre el equipo donde nuestro software se ejecutara, esa informacion asociada con algun perfilado (de esto hablaremos mas adelante) nos permite establecer el tamaño correcto para nuestro pool.
El Global Interpeter Lock
El GIL, abreviatura de Global Interpeter Lock, es un mutex (exclusion mutua) que protege a los objetos de python, evitando que multiples threads ejecuten codigos de bytes de python de una vez, esto significa que podemos escribir un codigo multithreading en python pero en realidad solo hay un thread corriendo (por proceso), esto es normalmente visto como una limitacion no deseada del lenguaje y muchos desarrolladores lo tildan como un gran villano, aunque esto puede ser removido y nos tomara un dia de trabajo, el precio a pagar puede ser un poco elevado dado que debemos aplicar un lock donde sean necesarios en el codigo, este conduce a un procesamiento mas costoso dado que una gran cantidad de locks individules toman mas tiempo en ser adquiridos y liberados, y sobre todo nos introduce un riesgo mas grande de tener errores o como se lo conocen en informatica: bugs, como se daran cuenta escribir un codigo multithreading no es sencillo y tendra que escribir decenas o cientos de locks, pero para entender mejor que son los locks y porque deben usarse, necesitamos hablar primero sobre uno de los riesgos mas importantes de multithreading como son las condiciones de carrera pero de esto hablaremos en el proximo post.
En resumen, hoy hemos visto que es un thread, si bien es de una manera muy superficial, nos serivra para entender la diferencia entre un thread y un proceso, tambien vimos como es su anatomia, sus dos niveles, los distintos estados que puede adoptar, que sucede si asesinamos o eliminamos un thread, que es un cambio de contexto y por ultimo hablamos de GIL, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
