Pregunta

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

Una gran cantidad de ruido se ha hecho en torno a la eliminación de la GIL desde Python, y me gustaría entender por qué esto es tan importante. Nunca he escrito un compilador ni un intérprete a mí mismo, así que no ser frugal con detalles, probablemente ellos tendrá que entender.

¿Fue útil?

Solución

GIL de Python está destinado para serializar el acceso a los componentes internos de interpretación de diferentes temas. En los sistemas multi-núcleo, significa que múltiples hilos no pueden efectivamente hacer uso de múltiples núcleos. (Si el GIL no dio lugar a este problema, la mayoría de las personas no se preocupan por el GIL - que sólo está siendo criado como un problema debido a la creciente prevalencia de los sistemas multi-núcleo.) Si se quiere entender en detalle, se puede ver este video o buscar la dirección este conjunto de diapositivas . Puede ser que sea demasiada información, pero entonces se pregunte por los detalles: -)

Tenga en cuenta que GIL de Python sólo es realmente un problema para CPython, la implementación de referencia. Jython y IronPython no tienen una GIL. Como desarrollador de Python, que por lo general no se encuentra con el GIL menos que esté escribiendo una extensión C. escritores de extensión C necesitan para liberar el GIL cuando sus extensiones de hacer el bloqueo de E / S, de modo que otros hilos en el proceso de Python tienen la oportunidad de funcionar.

Otros consejos

Supongamos que tiene varios hilos que no lo hacen realmente tocar los datos de cada uno. Estos deberían ejecutar la mayor independencia posible. Si usted tiene un "bloqueo global", que es necesario adquirir con el fin de (digamos) llamar a una función, que puede terminar como un cuello de botella. Usted puede terminar no conseguir mucho beneficio de tener múltiples hilos en el primer lugar.

Para ponerlo en una analogía del mundo real: imagine 100 desarrolladores que trabajan en una empresa con una sola taza de café. La mayoría de los desarrolladores sería gastar su tiempo de espera para el café en lugar de codificar.

Nada de esto es específico de Python - No sé los detalles de lo que necesitaba un pitón GIL en el primer lugar. Sin embargo, es de esperar que le ha dado una idea más clara del concepto general.

Primero vamos a entender lo que el pitón GIL ofrece:

Cualquier operación / instrucción se ejecuta en el intérprete. GIL asegura que intérprete se lleva a cabo por un solo hilo a un instante particular de tiempo . Y su programa de pitón con múltiples hilos trabaja en un solo intérprete. En cualquier instante particular de tiempo, este intérprete se lleva a cabo por un solo hilo. Esto significa que sólo el hilo que está sosteniendo el intérprete es ejecutar a cualquier instante de tiempo .

Ahora ¿por qué es un problema:

Su máquina podría estar teniendo múltiples núcleos / procesadores. Y múltiples núcleos permiten múltiples hilos para ejecutar al mismo tiempo es decir varios subprocesos pueden ejecutar en cualquier instante de tiempo determinado. . Pero ya 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, usted no está recibiendo ninguna ventaja proporcionada por múltiples núcleos, porque en cualquier instante un solo núcleo, que es el núcleo siendo utilizado por el hilo que ocupa actualmente la intérprete, se está utilizando. Por lo tanto, el programa tomará el tiempo para ejecutar como si fuera un solo programa roscado.

Sin embargo, potencialmente bloqueo o operaciones de larga duración, tales como O, procesamiento de E / imagen y el número NumPy crujido, sucede fuera de la GIL. Tomado de href="https://wiki.python.org/moin/GlobalInterpreterLock" aquí . Así que para este tipo de operaciones, una operación multiproceso todavía será más rápido que una sola operación de roscado a pesar de la presencia de GIL. Así, 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 deben ser posibles en ellos, pensé que nunca he utilizado PyPy y Jython y no está seguro de ello.

Python no permite multi-threading en el verdadero sentido de la palabra. Tiene un paquete multi-threading, pero si quieres a la multi-hilo para acelerar su código de arriba, entonces no es generalmente una buena idea para usarlo. Python tiene una construcción llamada la intérprete de bloqueo global (GIL).

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

El GIL se asegura de que sólo uno de sus hilos '' puede ejecutar en cualquier momento. Un hilo adquiere el GIL, hace un poco de trabajo, a continuación, pasa el GIL a la siguiente hilo. Esto sucede muy rápidamente por lo que el ojo humano puede parecer que los hilos se ejecutan en paralelo, pero en realidad son simplemente tomando turnos con el mismo núcleo de la CPU. Todo esto pasa GIL implica una sobrecarga de ejecución. Esto significa que si usted desea hacer que su código se ejecute más rápido que utilizando el paquete de roscado a menudo no es una buena idea.

Hay razones para utilizar el paquete de enhebrado de Python. Si desea ejecutar algunas cosas a la vez, y la eficiencia no es una preocupación, entonces es totalmente bien y conveniente. O si está ejecutando código que tiene que esperar a que algo (como un IO), entonces se podría hacer mucho sentido. Sin embargo, la librería de hilos no va dejar que utiliza núcleos de CPU adicionales.

Multi-threading puede ser subcontratada al sistema operativo (haciendo multi-procesamiento), alguna aplicación externa que llama a su código Python (por ejemplo, chispas o Hadoop), o algún código que sus Python llamadas de código (por ejemplo: usted podría tener su código Python llamada de una función C que hace el costoso material multi-hilo).

Siempre que dos hilos tienen acceso a la misma variable que tiene un problema. En C ++, por ejemplo, la forma de evitar el problema es definir algún bloqueo mutex para evitar que dos hilos a, digamos, introduzca el colocador de un objeto al mismo tiempo.

Multithreading es posible en pitón, pero dos hilos no se puede ejecutar al mismo tiempo a una granularidad más fina de una instrucción pitón. El hilo conductor es conseguir un bloqueo global llamada GIL.

Esto significa que si usted comienza a escribir código multiproceso con el fin de tomar ventaja de su procesador multi-nú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, pero a parte de su intérprete, incluyendo el CPython más común. (#Edited, ver comentario)

La cuestión GIL sigue siendo válida en Python 3000.

Python 3.7 documentación

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

  

detalle de implementación CPython: En CPython, debido a la intérprete de bloqueo global, sólo un hilo puede ejecutar código Python a la vez (a pesar de que algunas librerías orientadas al rendimiento podrían superar esta limitación). Si desea que su aplicación para hacer un mejor uso de los recursos computacionales de máquinas multi-núcleo, se aconseja utilizar multiprocessing o concurrent.futures.ProcessPoolExecutor. Sin embargo, roscado sigue siendo un modelo apropiado si desea ejecutar varias tareas de E / S enlazado al mismo tiempo.

Esto enlaza con la del Glosario para global interpreter lock lo que explica que el GIL implica que el paralelismo roscado en Python no es adecuado para tareas obligado CPU :

  

El mecanismo utilizado por el intérprete CPython para asegurar que sólo un hilo ejecuta Python bytecode a la vez. Esto simplifica la implementación CPython haciendo que el modelo de objetos (incluidos los tipos críticos incorporadas tales como dict) implícitamente seguras contra el acceso concurrente. Bloqueo de todo el intérprete hace que sea más fácil para el intérprete para ser multi-hilo, a expensas de la mayor parte del paralelismo ofrecida por las máquinas con múltiples procesadores.

     

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

     

Más allá de los esfuerzos para crear un intérprete “de subprocesamiento libre” (una que los bloqueos compartidos de datos con una granularidad mucho más fina) no han tenido éxito porque el rendimiento sufrió en el caso de un solo procesador común. Se cree que la superación de este problema de rendimiento haría que la aplicación mucho más complicado y por lo tanto más costoso de mantener.

Esta cita también implica que predice y por lo tanto la asignación de variables también es seguro para subprocesos como un detalle de implementación CPython:

A continuación, los documentos href="https://docs.python.org/3.7/library/multiprocessing.html#introduction" rel="nofollow para el paquete multiprocessing explicar cómo supera el GIL por el proceso de desove mientras se expone una interfaz similar a la de threading:

  

multiprocesamiento es un paquete que apoya los procesos de desove mediante una API similar al módulo de roscado. El paquete de multiprocesamiento ofrece tanto la concurrencia local y remota, de manera efectiva un lado las Intérprete bloqueo global mediante el uso de subprocesos en lugar de hilos. Debido a esto, el módulo de multiprocesamiento permite al programador aprovechar al máximo los procesadores múltiples en una máquina determinada. Se ejecuta en Unix y Windows.

Y las para concurrent.futures.ProcessPoolExecutor explican que utiliza multiprocessing como backend:

  

La clase es una subclase ProcessPoolExecutor Ejecutor que utiliza un conjunto de procesos para ejecutar llamadas de forma asíncrona. ProcessPoolExecutor utiliza el módulo de multiprocesamiento, lo que le permite esquivar el Global intérprete de bloqueo, pero también significa que sólo los objetos estibables se pueden ejecutar y devueltos.

que debe ser contrastado con el otro ThreadPoolExecutor clase base que utiliza hilos en lugar de los procesos de

  

ThreadPoolExecutor es una subclase Ejecutor que utiliza un grupo de subprocesos para ejecutar llamadas de forma asíncrona.

partir de la cual se concluye que ThreadPoolExecutor sólo es adecuado para / o tareas encuadernados I, mientras ProcessPoolExecutor también puede manejar la CPU tareas encuadernados.

La siguiente pregunta es ¿por qué el GIL existe en el primer lugar: Por qué el Intérprete de bloqueo global?

Proceso vs experimentos de rosca

En multiprocesamiento vs Threading de Python he hecho un análisis experimental de proceso vs hilos en Python.

vista previa rápida de los resultados:

introducir descripción de la imagen aquí

¿Por qué Python (CPython y otros) utiliza el GIL

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

En CPython, el cierre global del intérprete, o GIL, es un mutex que evita múltiples subprocesos nativos de la ejecución de los códigos de bytes de Python a la vez. Este bloqueo es necesaria debido principalmente a la gestión de memoria de CPython no es seguro para subprocesos.

¿Cómo sacarlo de Python?

Al igual que Lua, Python quizá podría comenzar múltiples VM, pero Python no hace eso, supongo que debe haber algunas otras razones.

En Numpy o alguna otra biblioteca de Python extendida, a veces, liberando el GIL a otros hilos podría aumentar la eficacia de todo el programa.

Quiero compartir un ejemplo de la multihilo libro para efectos visuales. Así que aquí hay una situación de bloqueo de la muerte clásico

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

Ahora considerar los acontecimientos en la secuencia resultante de un impasse.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
║   ║ 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                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top