¿Por qué se escribió Python con el GIL?

112

El bloqueo global del intérprete (GIL) parece ser a menudo citado como una de las principales razones por las que el enhebrado y cosas similares son un poco difíciles en Python, lo que plantea la pregunta "¿Por qué se hizo eso en primer lugar?"

Al no ser un programador, no tengo idea de por qué podría ser eso: ¿cuál era la lógica detrás de poner el GIL?

Fomite
fuente
10
El artículo de Wikipedia dice que "el GIL puede ser una barrera significativa para el paralelismo, un precio pagado por tener el dinamismo del lenguaje" , y continúa diciendo que "Las razones para emplear dicho bloqueo incluyen: mayor velocidad de los programas de un solo subproceso (no es necesario adquirir o liberar bloqueos en todas las estructuras de datos por separado) y una fácil integración de bibliotecas C que generalmente no son seguras para subprocesos ".
Robert Harvey
3
@RobertHarvey, Dynamism no tiene nada que ver con eso. El problema es la mutación.
dan_waterworth
stackoverflow.com/questions/265687/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
1
No puedo evitar sentir que, como la falta de números no firmados de Java, pretendía evitar que las personas que no saben lo que están haciendo se disparen en el pie. Por desgracia, cualquier persona que no saben lo que están haciendo consigue un lenguaje deficiente, lo que es una verdadera pena porque rocas Python en muchas otras formas
Basic
1
@Basic tiene que haber alguna forma estándar de lidiar con los conjuntos de bytes en Java (no lo he usado en mucho tiempo) para hacer matemática criptográfica. Python (por ejemplo) no tiene números con signo, pero ni siquiera intentaría hacer operaciones bit a bit con él porque hay mejores formas.
Nick T

Respuestas:

105

Hay varias implementaciones de Python, por ejemplo, CPython, IronPython, RPython, etc.

Algunos de ellos tienen un GIL, otros no. Por ejemplo, CPython tiene el GIL:

De http://en.wikipedia.org/wiki/Global_Interpreter_Lock

Las aplicaciones escritas en lenguajes de programación con un GIL pueden diseñarse para usar procesos separados para lograr un paralelismo completo, ya que cada proceso tiene su propio intérprete y, a su vez, tiene su propio GIL.

Beneficios de la GIL

  • Mayor velocidad de los programas de un solo subproceso.
  • Fácil integración de bibliotecas C que generalmente no son seguras para subprocesos.

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

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.

El GIL es controvertido porque evita que los programas multiproceso de CPython aprovechen al máximo los sistemas multiprocesador en ciertas situaciones. Tenga en cuenta que las operaciones potencialmente bloqueadoras o de larga duración, como E / S, procesamiento de imágenes y procesamiento de números NumPy, ocurren fuera de GIL. Por lo tanto, solo en programas multiproceso que pasan mucho tiempo dentro del GIL, interpretando el código de bytes de CPython, el GIL se convierte en un cuello de botella.

Python tiene un GIL en oposición al bloqueo de grano fino por varias razones:

  • Es más rápido en el caso de un solo subproceso.

  • Es más rápido en el caso de subprocesos múltiples para programas vinculados de E / S.

  • Es más rápido en el caso de subprocesos múltiples para programas vinculados a la CPU que realizan su trabajo intensivo en cómputo en bibliotecas C.

  • Hace que las extensiones en C sean más fáciles de escribir: no habrá cambio de subprocesos de Python excepto donde permita que ocurra (es decir, entre las macros Py_BEGIN_ALLOW_THREADS y Py_END_ALLOW_THREADS).

  • Facilita el ajuste de las bibliotecas C No tiene que preocuparse por la seguridad del hilo. Si la biblioteca no es segura para subprocesos, simplemente mantenga el GIL bloqueado mientras lo llama.

El GIL puede ser lanzado por extensiones C. La biblioteca estándar de Python libera el GIL alrededor de cada llamada de bloqueo de E / S. Por lo tanto, el GIL no tiene consecuencias para el rendimiento de los servidores vinculados de E / S. Por lo tanto, puede crear servidores de red en Python utilizando procesos (fork), subprocesos o E / S asíncronas, y el GIL no se interpondrá en su camino.

Las bibliotecas numéricas en C o Fortran se pueden llamar de manera similar con el GIL lanzado. Mientras su extensión C está esperando que se complete un FFT, el intérprete ejecutará otros hilos de Python. Un GIL es, por lo tanto, más fácil y rápido que el bloqueo de grano fino en este caso también. Esto constituye la mayor parte del trabajo numérico. La extensión NumPy libera el GIL siempre que sea posible.

Los hilos suelen ser una mala forma de escribir la mayoría de los programas de servidor. Si la carga es baja, la bifurcación es más fácil. Si la carga es alta, la E / S asíncrona y la programación controlada por eventos (por ejemplo, utilizando el marco Twisted de Python) es mejor. La única excusa para usar hilos es la falta de os.fork en Windows.

El GIL es un problema si, y solo si, está haciendo un trabajo intensivo de CPU en Python puro. Aquí puede obtener un diseño más limpio utilizando procesos y transmisión de mensajes (por ejemplo, mpi4py). También hay un módulo de 'procesamiento' en la tienda de queso Python, que brinda a los procesos la misma interfaz que los subprocesos (es decir, reemplace threading.Thread con Processing.Process).

Los subprocesos se pueden usar para mantener la capacidad de respuesta de una GUI independientemente de la GIL. Si el GIL perjudica su rendimiento (vea la discusión anterior), puede dejar que su hilo genere un proceso y esperar a que termine.

Md Mahbubur Rahman
fuente
52
A mí me suena a uvas agrias. Python no puede hacer hilos correctamente, por lo que inventa razones por las cuales los hilos son innecesarios o incluso malos. "Si la carga es baja, bifurcar es más fácil", ¿en serio? Y el GIL es "más rápido" para todos esos casos solo si insiste en usar GC de conteo de referencias.
Michael Borgwardt
99
s/RPython/PyPy/g. @MichaelBorgwardt Dando razones pro GIL es el punto de la pregunta, ¿no? Aunque estaría de acuerdo en que algunos de los contenidos de esta respuesta (es decir, la discusión de alternativas) no viene al caso. Y para bien o para mal, ahora es casi imposible deshacerse del recuento: está profundamente arraigado en toda la API y la base de código; Es casi imposible deshacerse de él sin reescribir la mitad del código y romper todo el código externo.
10
No olvides la multiprocessingbiblioteca, estándar desde 2.6. Sus grupos de trabajadores son una abstracción súper hábil para algunos tipos simples de paralelismo.
Sean McSomething
8
@alcalde Solo si no sabe lo que está haciendo y / o no quiere que sus hilos puedan trabajar cooperativamente / comunicarse. De lo contrario, es un dolor real en la parte trasera, especialmente teniendo en cuenta la sobrecarga de lanzar un nuevo proceso en algunos sistemas operativos. Tenemos servidores con 32 núcleos, por lo que para utilizarlos completamente en CPython necesitaría 32 procesos. Esa no es una "buena solución", es un truco para solucionar las deficiencias de CPython.
Básico
8
El hecho de que existan hilos en plataformas que no sean Windows debería ser prueba suficiente de que la bifurcación no es adecuada en todas las situaciones.
zneak
42

En primer lugar: Python no tiene un GIL. Python es un lenguaje de programación. Un lenguaje de programación es un conjunto de reglas y restricciones matemáticas abstractas. No hay nada en la especificación del lenguaje Python que diga que debe haber un GIL.

Hay muchas implementaciones diferentes de Python. Algunos tienen un GIL, otros no.

Una explicación simple para tener un GIL es que escribir código concurrente es difícil. Al colocar un candado gigante alrededor de su código, lo obliga a ejecutarse siempre en serie. ¡Problema resuelto!

En CPython, en particular, un objetivo importante es facilitar la extensión del intérprete con complementos escritos en C. Una vez más, escribir código concurrente es difícil, por lo que al garantizar que no habrá concurrencia, es más fácil escribir extensiones para el interprete. Además, muchas de esas extensiones son solo envoltorios delgados alrededor de las bibliotecas existentes que pueden no haberse escrito teniendo en cuenta la concurrencia.

Jörg W Mittag
fuente
66
Ese es el mismo argumento que la falta de tipos numéricos sin signo de Java - los desarrolladores creen que todos los demás es más tonto de lo que son ...
Básico
1
@Basic: lo creas o no, incluso cuando no eres realmente, realmente tonto, resulta que tener un lenguaje que haga suposiciones simplificadoras que significan que no piensas en ciertas cosas para que funcionen sigue siendo útil cosa. CPython es excelente para ciertas cosas, incluidas las aplicaciones multiproceso simples (donde el programa está vinculado a IO, que son muchas y, por lo tanto, el GIL no importa), porque las decisiones de diseño que hicieron del GIL la mejor solución también facilitan la programación de esas aplicaciones , particularmente el hecho de que admite operaciones atómicas en colecciones .
Julio
@Jules Sí, es muy útil hasta que necesite esas capacidades. La solución "preferida" de cpython de "solo escríbalo en otro idioma como c ++" significa que pierde todos los beneficios de Python. Si está escribiendo la mitad de su código en c ++, ¿por qué comenzar desde Python? Claro, para proyectos pequeños de API / pegamento es rápido y fácil, y para ETL es insuperable, pero no es adecuado para nada que requiera levantar objetos pesados. Lo mismo que usar Java para hablar con el hardware ... Es casi cómico los aros por los que tienes que saltar.
Básico
16

¿Cuál es el propósito de un GIL?

La documentación de CAPI tiene esto que decir sobre el tema:

El intérprete de Python no es completamente seguro para subprocesos. Para admitir programas Python multiproceso, hay un bloqueo global, llamado bloqueo global del intérprete o GIL, que debe mantener el hilo actual antes de que pueda acceder de forma segura a los objetos Python. Sin el bloqueo, incluso las operaciones más simples podrían causar problemas en un programa de subprocesos múltiples: por ejemplo, cuando dos subprocesos incrementan simultáneamente el recuento de referencia del mismo objeto, el recuento de referencia podría terminar incrementándose solo una vez en lugar de dos veces.

En otras palabras, el GIL previene la corrupción del estado. Los programas de Python nunca deberían producir una falla de segmentación, porque solo se permiten operaciones seguras de memoria. El GIL extiende esta garantía a programas multiproceso.

Cuales son las alternativas?

Si el propósito de la GIL es proteger al estado de la corrupción, entonces una alternativa obvia es asegurar un grano mucho más fino; quizás en un nivel por objeto. El problema con esto es que, aunque se ha demostrado que aumenta el rendimiento de los programas de subprocesos múltiples, como resultado tiene más gastos generales y los programas de subprocesos únicos sufren.

dan_waterworth
fuente
2
Sería genial permitir que un usuario ejecute un programa con una opción de intérprete que reemplace el gil por bloqueo de grano fino, y de alguna manera sepa, de manera de solo lectura, si el proceso actual se generó con o sin gil.
Luis Masuelli
A pesar de GIL, logré producir una falla de segmentación en un programa multiproceso debido al uso descuidado del módulo pyodbc. Por lo tanto, "nunca debe producir una falla de segmentación" es una falacia.
Muposat