¿Qué es el bloqueo global de intérprete (GIL) en CPython?

244

¿Qué es un bloqueo global de intérprete y por qué es un problema?

Se ha hecho mucho ruido al eliminar el GIL de Python, y me gustaría entender por qué eso es tan importante. Nunca he escrito un compilador ni un intérprete, así que no seas frugal con los detalles, probablemente los necesite para entender.

e-satis
fuente
3
Mire a David Beazley decirle todo lo que siempre quiso saber sobre el GIL.
hughdbrown
1
Aquí hay un artículo largo que habla sobre el GIL y el subproceso en Python que escribí hace un tiempo. Entra
jnoller
Aquí hay algunos códigos que demuestran los efectos de GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu
3
Creo que esta es la mejor explicación de GIL. Por favor lee. dabeaz.com/python/UnderstandingGIL.pdf
suhao399
realpython.com/python-gil Encontré esto útil
qwr

Respuestas:

220

Python GIL está destinado a serializar el acceso a intérpretes internos de diferentes hilos. En sistemas de múltiples núcleos, significa que múltiples hilos no pueden hacer uso efectivo de múltiples núcleos. (Si el GIL no condujo a este problema, la mayoría de las personas no se preocuparían por el GIL; solo se plantea como un problema debido a la creciente prevalencia de los sistemas de múltiples núcleos). Si desea comprenderlo en detalle, puedes ver este video o mirar este conjunto de diapositivas . Puede ser demasiada información, pero luego solicitó detalles :-)

Tenga en cuenta que Python de GIL solo es realmente un problema para CPython, la implementación de referencia. Jython y IronPython no tienen un GIL. Como desarrollador de Python, generalmente no te encuentras con el GIL a menos que estés escribiendo una extensión C. Los creadores de extensiones C necesitan liberar el GIL cuando sus extensiones bloquean las E / S, para que otros hilos en el proceso Python tengan la oportunidad de ejecutarse.

Vinay Sajip
fuente
46
Buena respuesta: básicamente significa que los hilos en Python solo son buenos para bloquear E / S; su aplicación nunca superará 1 núcleo de CPU del uso del procesador
Ana Betts
8
"Como desarrollador de Python, generalmente no te encuentras con el GIL a menos que estés escribiendo una extensión en C". Es posible que no sepas que la causa de que tu código de subprocesos múltiples se ejecute a paso de tortuga es el GIL, pero tú ' Ciertamente sentiré sus efectos. Todavía me sorprende que aprovechar un servidor de 32 núcleos con Python significa que necesito 32 procesos con toda la sobrecarga asociada.
Básico
66
@PaulBetts: no es cierto. Es probable que el rendimiento del código crítico ya utiliza extensiones C que pueden y liberar GIL por ejemplo, regex, lxml, numpymódulos. Cython permite liberar GIL en código personalizado, por ejemplo,b2a_bin(data)
jfs
55
@Paul Betts: puede obtener más de 1 código de CPU del uso del procesador utilizando el módulo de multiprocesamiento . Crear múltiples procesos es "más pesado" que crear múltiples subprocesos, pero si realmente necesita hacer el trabajo en paralelo, en Python, es una opción.
AJNeufeld
1
@david_adler Sí, sigue siendo el caso, y es probable que lo siga siendo por un tiempo todavía. Eso realmente no ha impedido que Python sea realmente útil para muchas cargas de trabajo diferentes.
Vinay Sajip
59

Supongamos que tiene múltiples hilos que realmente no tocan los datos del otro. Esos deberían ejecutarse de la manera más independiente posible. Si tiene un "bloqueo global" que necesita adquirir para (por ejemplo) llamar a una función, eso puede terminar como un cuello de botella. Puede terminar sin obtener muchos beneficios al tener múltiples hilos en primer lugar.

Para ponerlo en una analogía del mundo real: imagine a 100 desarrolladores trabajando en una empresa con una sola taza de café. La mayoría de los desarrolladores pasarían su tiempo esperando café en lugar de codificar.

Nada de esto es específico de Python: en primer lugar, no sé los detalles de para qué Python necesitaba un GIL. Sin embargo, con suerte le dará una mejor idea del concepto general.

Jon Skeet
fuente
Excepto que esperar la taza de café parece un proceso bastante vinculado a E / S, ya que seguramente pueden hacer otras cosas mientras esperan la taza. El GIL tiene muy poco efecto en los hilos pesados ​​de E / S que de todos modos pasan la mayor parte del tiempo esperando.
Cruncher
36

Primero entendamos lo que proporciona Python GIL:

Cualquier operación / instrucción se ejecuta en el intérprete. GIL asegura que el intérprete esté en manos de un solo hilo en un instante de tiempo particular . Y su programa Python con múltiples hilos funciona en un solo intérprete. En cualquier instante particular de tiempo, este intérprete está en manos de un solo hilo. Significa que solo el hilo que contiene el intérprete se está ejecutando en cualquier momento .

Ahora, ¿por qué es eso un problema?

Su máquina podría tener múltiples núcleos / procesadores. Y múltiples núcleos permiten que múltiples hilos se ejecuten simultáneamente, es decir, múltiples hilos podrían ejecutarse en cualquier instante de tiempo en particular. . Pero dado que el intérprete está en manos de un solo hilo, otros hilos no están haciendo nada a pesar de que tienen acceso a un núcleo. Por lo tanto, no está obteniendo ninguna ventaja proporcionada por múltiples núcleos porque en un instante solo se está utilizando un solo núcleo, que es el núcleo utilizado por el hilo que actualmente contiene el intérprete. Por lo tanto, su programa tardará tanto en ejecutarse como si fuera un solo programa de subprocesos.

Sin embargo, las operaciones potencialmente de bloqueo o de larga duración, como E / S, procesamiento de imágenes y procesamiento de números NumPy, ocurren fuera del GIL. Tomado de aquí . Entonces, para tales operaciones, una operación multiproceso aún será más rápida que una operación de un solo subproceso a pesar de la presencia de GIL. Entonces, GIL no siempre es un cuello de botella.

Editar: GIL es un detalle de implementación de CPython. IronPython y Jython no tienen GIL, por lo que un programa verdaderamente multiproceso debería ser posible en ellos, pensé que nunca había usado PyPy y Jython y no estoy seguro de esto.

Akshar Raaj
fuente
44
Nota : PyPy tiene el GIL . Referencia : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . Mientras que Ironpython y Jython no tienen el GIL.
Tasdik Rahman
De hecho, PyPy tiene un GIL, pero IronPython no.
Emmanuel
@Emmanuel Editó la respuesta para eliminar PyPy e incluir IronPython.
Akshar Raaj
17

Python no permite subprocesos múltiples en el verdadero sentido de la palabra. Tiene un paquete de subprocesos múltiples, pero si desea varios subprocesos para acelerar su código, generalmente no es una buena idea usarlo. Python tiene una construcción llamada Global Interpreter Lock (GIL).

https://www.youtube.com/watch?v=ph374fJqFPE

El GIL se asegura de que solo uno de sus 'hilos' pueda ejecutarse a la vez. Un hilo adquiere el GIL, hace un poco de trabajo, luego pasa el GIL al siguiente hilo. Esto sucede muy rápido, por lo que para el ojo humano puede parecer que sus subprocesos se ejecutan en paralelo, pero en realidad solo se turnan para usar el mismo núcleo de CPU. Todo este paso de GIL agrega gastos generales a la ejecución. Esto significa que si desea que su código se ejecute más rápido, usar el paquete de subprocesos a menudo no es una buena idea.

Hay razones para usar el paquete de subprocesos de Python. Si desea ejecutar algunas cosas simultáneamente, y la eficiencia no es una preocupación, entonces está totalmente bien y conveniente. O si está ejecutando código que necesita esperar algo (como algunos IO), entonces podría tener mucho sentido. Pero la biblioteca de subprocesos no le permitirá usar núcleos de CPU adicionales.

El subprocesamiento múltiple se puede externalizar al sistema operativo (mediante el procesamiento múltiple), alguna aplicación externa que llame a su código de Python (por ejemplo, Spark o Hadoop), o algún código al que llame su código de Python (por ejemplo: podría tener su Python llamada de código a una función C que hace el costoso material de subprocesos múltiples).

Ijaz Ahmad Khan
fuente
15

Cada vez que dos hilos tienen acceso a la misma variable, tiene un problema. En C ++, por ejemplo, la forma de evitar el problema es definir un bloqueo de mutex para evitar que dos hilos entren, digamos, al mismo tiempo al establecedor de un objeto.

El subprocesamiento múltiple es posible en python, pero no se pueden ejecutar dos subprocesos al mismo tiempo con una granularidad más fina que una instrucción de python. El hilo en ejecución está obteniendo un bloqueo global llamado GIL.

Esto significa que si comienza a escribir un código multiproceso para aprovechar su procesador multinúcleo, su rendimiento no mejorará. La solución habitual consiste en ir multiproceso.

Tenga en cuenta que es posible liberar el GIL si está dentro de un método que escribió en C, por ejemplo.

El uso de un GIL no es inherente a Python sino a algunos de sus intérpretes, incluido el CPython más común. (#editado, ver comentario)

El problema de GIL todavía es válido en Python 3000.

fulmicoton
fuente
Stackless todavía tiene un GIL. Stackless no mejora el subprocesamiento (como en el módulo): ofrece un método diferente de programación (corutinas) que intentan evitar el problema, pero requieren funciones sin bloqueo.
jnoller
¿Qué pasa con el nuevo GIL en 3.2?
nuevo123456
Solo para agregar que no tiene un problema / necesita mutexes / semáforos si solo un hilo actualizará la memoria. @ new123456 reduce la contención y programa mejor los subprocesos sin dañar el rendimiento de un solo subproceso (que es impresionante en sí mismo) pero sigue siendo un bloqueo global.
Básico
14

Documentación de Python 3.7

También me gustaría destacar la siguiente cita de la documentación de Pythonthreading :

Detalle de implementación de CPython: en CPython, debido al Bloqueo global del intérprete, solo un subproceso puede ejecutar código Python a la vez (aunque ciertas bibliotecas orientadas al rendimiento pueden superar esta limitación). Si desea que su aplicación haga un mejor uso de los recursos computacionales de las máquinas multinúcleo, se recomienda usar multiprocessingo concurrent.futures.ProcessPoolExecutor. Sin embargo, el subproceso sigue siendo un modelo apropiado si desea ejecutar varias tareas vinculadas de E / S simultáneamente.

Esto enlaza con la entradaglobal interpreter lock del Glosario para la cual se explica que GIL implica que el paralelismo roscado en Python no es adecuado para tareas vinculadas a la CPU :

El mecanismo utilizado por el intérprete de CPython para garantizar que solo un subproceso ejecute el código de bytes de Python a la vez. Esto simplifica la implementación de CPython al hacer que el modelo de objetos (incluidos los tipos incorporados críticos como dict) esté implícitamente seguro contra el acceso concurrente. El bloqueo de todo el intérprete facilita que el intérprete sea multiproceso, a expensas de gran parte del paralelismo que brindan las máquinas multiprocesador.

Sin embargo, algunos módulos de extensión, ya sean estándar o de terceros, están diseñados para liberar el GIL cuando se realizan tareas computacionalmente intensivas, como la compresión o el hash. Además, el GIL siempre se libera al hacer E / S.

Los esfuerzos anteriores para crear un intérprete de "subproceso libre" (uno que bloquea los datos compartidos con una granularidad mucho más fina) no han tenido éxito porque el rendimiento se vio afectado en el caso común de un solo procesador. Se cree que superar este problema de rendimiento haría que la implementación sea mucho más complicada y, por lo tanto, más costosa de mantener.

Esta cita también implica que los dictados y, por lo tanto, la asignación de variables también son seguros para subprocesos como un detalle de implementación de CPython:

A continuación, los documentos del multiprocessingpaquete. explican cómo supera el GIL al generar el proceso al tiempo que expone una interfaz similar a la de threading:

El multiprocesamiento es un paquete que admite procesos de generación utilizando una API similar al módulo de subprocesos. El paquete de multiprocesamiento ofrece concurrencia local y remota, evitando de manera efectiva el bloqueo global del intérprete mediante el uso de subprocesos en lugar de subprocesos. Debido a esto, el módulo de multiprocesamiento le permite al programador aprovechar al máximo múltiples procesadores en una máquina determinada. Se ejecuta tanto en Unix como en Windows.

Y los documentosconcurrent.futures.ProcessPoolExecutor explican que se usa multiprocessingcomo backend:

La clase ProcessPoolExecutor es una subclase de ejecutor que utiliza un conjunto de procesos para ejecutar llamadas de forma asincrónica. ProcessPoolExecutor utiliza el módulo de multiprocesamiento, que le permite eludir el bloqueo global del intérprete, pero también significa que solo se pueden ejecutar y devolver objetos seleccionables.

que debería contrastarse con la otra clase base ThreadPoolExecutorque usa hilos en lugar de procesos

ThreadPoolExecutor es una subclase de ejecutor que utiliza un grupo de subprocesos para ejecutar llamadas de forma asincrónica.

de lo cual concluimos que ThreadPoolExecutorsolo es adecuado para tareas vinculadas de E / S, mientras ProcessPoolExecutorque también puede manejar tareas vinculadas a la CPU.

La siguiente pregunta pregunta por qué existe el GIL en primer lugar: ¿Por qué el Global Interpreter Lock?

Experimentos de proceso vs hilo

En Multiprocessing vs Threading Python he hecho un análisis experimental de proceso vs threads en Python.

Vista previa rápida de los resultados:

ingrese la descripción de la imagen aquí

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
0

Por qué Python (CPython y otros) usa el GIL

De http://wiki.python.org/moin/GlobalInterpreterLock

En CPython, el bloqueo global del intérprete, o GIL, es un mutex que evita que múltiples hilos nativos ejecuten códigos de bytes Python a la vez. Este bloqueo es necesario principalmente porque la administración de memoria de CPython no es segura para subprocesos.

¿Cómo eliminarlo de Python?

Al igual que Lua, quizás Python podría iniciar varias máquinas virtuales, pero python no hace eso, supongo que debería haber otras razones.

En Numpy o alguna otra biblioteca extendida de Python, a veces, liberar el GIL a otros subprocesos podría aumentar la eficiencia de todo el programa.

maoyang
fuente
0

Quiero compartir un ejemplo del libro multihilo para efectos visuales. Así que aquí hay una situación clásica de bloqueo muerto

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Ahora considere los eventos en la secuencia que resultan en un punto muerto.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    Main Thread                             Other Thread                         
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
 1  Python Command acquires GIL             Work started                         
 2  Computation requested                   MyCallback runs and acquires MyMutex 
 3                                          MyCallback now waits for GIL         
 4  MyCallback runs and waits for MyMutex   waiting for GIL                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
usuario1767754
fuente