Global Interpreter Locker/ GIL (Python)
Permite que sólo un thread tome el control del intérprete, es decir, que solo un thread puede estar en ejecución a la vez.
Esto rara vez tiene repercusiones para quienes desarrollamos programas utilizando solo el thread principal, para aquellas personas que desarrollan de forma concurrente este si puede llegar a ser un dolor de cabeza.
Pero, ¿Si GIL genera cuellos de botella, por qué se introdujo en primera instancia y por qué simplemente no se quita? Muy buenas preguntas, y para responderlas es necesario comprender como funciona Python internamente.
En términos simples Python posee un concepto llamado conteo de referencias, el cual le permite conocer al intérprete cuando un objeto está siendo utilizando y cuando no. Es algo bastante simple.
Por ejemplo, si mi variable A, posee referencias, por lo menos una o más, entonces, se concluye que la variable está siendo utilizada en alguna parte del programa. Por otro lado, si la variable no posee referencia, se concluye que no se está utilizando, y es allí donde entra el recolector de basura y libera la memoria.
Veamos un ejemplo.
import sys
A = 'Hola, soy una referencia'
sys.getrefcount(A)
>>> 2
En este casa obtenemos como resultado dos, ya que la variable A cuenta con dos referencias, la asignación y en el llamado a la función.
Pues bien, el trabajo de GIL es impedir que múltiples threads decrementen la referencia de algún objeto mientras otros están haciendo uso de ella. Como la naturaleza de un thread es trabajar de forma concurrente, en teoría, es posible que un thread le indique al intérprete que una variable se ha dejado de utilizar, cuando realmente otro thread aun sigue trabajando con ella.
Para evitar este problema se implementó GIL, permitiendo que sólo un thread tome el control del intérprete.
GIL no solo se resolvio el problema del conteo de referencias, también hizo que la implementación de Python sea mucho más sencilla, a la vez que incrementa la velocidad de ejecución cuando trabajamos con un único thread. En palabras de Larry Hastings: la decisión del diseño de GIL es una de las cosas que hizo que Python fuera tan popular como lo es hoy.
Ahora, ya sabemos que GIL previene que espacios en memoria sean liberados cuando aún están siendo utilizados, ok, pero, ¿no hay forma en crear algún otro mecanismo en el recolector de basura para evitar el problema? pues, dejame decirte que Guido van Rossum ,creador de Python dice que GIL esta aquí para quedarse.
Pero en esencia, eliminar GIL traería muchos más problemas que ventajas.
MULTIPROCESAMIENTOS
Pero no te preocupes, no todo está perdido. Si realmente queremos que las tareas se ejecuten de forma paralela debemos optar por el multiprocesamiento sobre el multithreading; de esta forma cada proceso tendrá su propio intérprete y podrá ejecutarse de manera independiente, logrando así evitar el cuello de botella de GIL y aprovechando todo el potencial de nuestros equipos.
La mejor forma de trabajar procesos en Python es sin duda con la librería multiprocessing.
Aquí un pequeño ejemplo de como crear nuestros propios Procesos. Haz la prueba por ti mismo y verás el resultado.
import time
import threading
import multiprocessing
def countdown(number):
while number > 0:
number -=1
if __name__ == '__main__':
start = time.time()
count = 100000000
t1 = multiprocessing.Process(target=countdown, args=(count,))
t2 = multiprocessing.Process(target=countdown, args=(count,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f'Tiempo transcurrido {time.time() - start }')
En mi notebook se demoro 8.x segundos!
Gracias por leer mis apuntes!