2008-09-19 6 views
59

Si confía en una implementación de Python que tiene un bloqueo de intérprete global (es decir, CPython) y escribe código multiproceso, ¿realmente necesita bloqueos?¿Los bloqueos son innecesarios en el código de Python de subprocesos múltiples debido a GIL?

Si el GIL no permite que se ejecuten varias instrucciones en paralelo, ¿no sería innecesario proteger los datos compartidos?

lo siento si esta es una pregunta tonta, pero es algo que siempre me he preguntado acerca de Python en máquinas multiprocesador/núcleo.

Lo mismo se aplicaría a cualquier otra implementación de idioma que tenga un GIL.

+0

También tenga en cuenta que el GIL y es detalle de implementación. IronPython y Jython, por ejemplo, no tienen un GIL. –

Respuesta

58

Aún necesitará bloqueos si comparte estado entre subprocesos. GIL solo protege al intérprete internamente. Aún puede tener actualizaciones inconsistentes en su propio código.

Por ejemplo:

#!/usr/bin/env python 
import threading 

shared_balance = 0 

class Deposit(threading.Thread): 
    def run(self): 
     for _ in xrange(1000000): 
      global shared_balance 
      balance = shared_balance 
      balance += 100 
      shared_balance = balance 

class Withdraw(threading.Thread): 
    def run(self): 
     for _ in xrange(1000000): 
      global shared_balance 
      balance = shared_balance 
      balance -= 100 
      shared_balance = balance 

threads = [Deposit(), Withdraw()] 

for thread in threads: 
    thread.start() 

for thread in threads: 
    thread.join() 

print shared_balance 

En este caso, el código se puede interrumpir la lectura entre el estado compartido (balance = shared_balance) y escribir el resultado cambió de nuevo (shared_balance = balance), causando una actualización perdida. El resultado es un valor aleatorio para el estado compartido.

Para hacer las actualizaciones consistentes, los métodos de ejecución necesitarían bloquear el estado compartido alrededor de las secciones de lectura-modificación-escritura (dentro de los bucles) o tener some way to detect when the shared state had changed since it was read.

+0

¡El ejemplo de código da una comprensión clara y visual! Buen post Harris! ¡Ojalá pudiera votar dos veces! – RayLuo

1

Aún necesita utilizar bloqueos (su código podría interrumpirse en cualquier momento para ejecutar otro hilo y esto puede causar inconsistencias en los datos). El problema con GIL es que evita que el código de Python use más núcleos al mismo tiempo (o múltiples procesadores si están disponibles).

21

No - GIL solo protege las partes internas de python de varios subprocesos que alteran su estado. Este es un nivel muy bajo de bloqueo, suficiente solo para mantener las propias estructuras de Python en un estado consistente. No cubre el bloqueo de nivel de la aplicación que deberá hacer para cubrir la seguridad de la secuencia en su propio código.

La esencia de bloqueo es asegurar que un bloque particular de código sólo se ejecuta por un hilo. El GIL aplica esto para bloquear el tamaño de un solo bytecode, pero generalmente quiere que el bloqueo abarque un bloque de código más grande que este.

6

Este post describe el GIL a un alto nivel bastante:

De particular interés son estas citas:

Cada diez instrucciones (este valor predeterminado puede ser cambiado), el núcleo libera el GIL para el hilo actual. En ese punto , el sistema operativo elige un hilo de todos los hilos que compiten por el bloqueo (posiblemente elegir el mismo hilo que acaba de publicar el GIL - no hacer tienen ningún control sobre qué hilo se elegido); ese hilo adquiere el GIL y luego se ejecuta para otros diez códigos de byte .

y

Nota cuidadosamente que el GIL sólo se restringe puro código Python. Extensiones (bibliotecas externas Python generalmente escritos en C) se puede escribir que liberar el bloqueo, lo que permite entonces el intérprete Python para ejecutar por separado de la extensión hasta la extensión vuelve a adquirir la cerradura.

suena como el GIL simplemente proporciona un menor número de casos posibles para un cambio de contexto, y hace varios núcleos/sistemas de procesador se comportan como un solo núcleo, con respecto a cada instancia pitón intérprete, de modo que sí, que todavía tienen que usar mecanismos de sincronización

+1

Nota: 'sys.getcheckinterval()' le indica el número de código de bytes instrucciones se ejecutan entre "GIL libera" (y ha sido 100 (no 10) desde al menos 2,5). En 3.2 puede estar cambiando a un intervalo basado en el tiempo (5 ms o menos) en lugar de conteos de instrucciones. El cambio también se puede aplicar a 2.7, aunque todavía es un trabajo en progreso. –

7

El bloqueo de intérprete global impide que los subprocesos accedan al intérprete al mismo tiempo (por lo que CPython solo utiliza un núcleo). Sin embargo, según tengo entendido, los hilos todavía están interrumpidos y programados de manera preventiva, lo que significa que aún necesita bloqueos en las estructuras de datos compartidas, para que sus hilos no pisen los dedos del otro.

La respuesta que he encontrado una y otra vez es que el multihilo en Python rara vez vale la sobrecarga, debido a esto. He escuchado cosas buenas sobre el proyecto PyProcessing, lo que hace que ejecutar múltiples procesos sea tan simple como multihilo, con estructuras de datos compartidas, colas, etc. (PyProcessing se introducirá en la biblioteca estándar del próximo Python 2.6 como el módulo multiprocessing .) Esto lo lleva al GIL, ya que cada proceso tiene su propio intérprete.

0

Un poco de actualización del ejemplo de Will Harris:

class Withdraw(threading.Thread): 
def run(self):    
    for _ in xrange(1000000): 
     global shared_balance 
     if shared_balance >= 100: 
      balance = shared_balance 
      balance -= 100 
      shared_balance = balance 

Deja un comunicado cheque valor en el retirarse y no veo negativo más y actualizaciones parece consistente. Mi pregunta es:

Si GIL impide que solo se pueda ejecutar un hilo en cualquier momento atómico, ¿dónde estaría el valor obsoleto? Si no hay valor obsoleto, ¿por qué necesitamos bloqueo? (Suponiendo que solo hablamos sobre el código python puro)

Si entiendo correctamente, la comprobación de condición anterior no funcionaría en un real ambiente de enhebrado. Cuando se ejecutan más de un subproceso al mismo tiempo, se puede crear un valor obsoleto, por lo tanto, la incoherencia del estado compartido, entonces realmente necesita un bloqueo.Pero si Python realmente solo permite un solo hilo en cualquier momento (time slicing threading), entonces no debería ser posible que exista un valor obsoleto, ¿no?

+0

Ok parece que GIL no bloquea el hilo todo el tiempo y el cambio de contexto aún podría suceder. Así que estoy equivocado, todavía se necesita bloqueo. – jimx

3

creo que de esta manera:

En un equipo de procesador único, multi-hilo pasa por suspensión de un hilo y empezar con otro lo suficientemente rápido como para que parezca estar funcionando al mismo tiempo. Esto es como Python con GIL: solo un hilo se está ejecutando.

El problema es que el hilo se puede suspender en cualquier lugar, por ejemplo, si quiero calcular b = (a + b) * 3, esto podría producir instrucciones de algo como esto:

1 a += b 
2 a *= 3 
3 b = a 

Ahora, Digamos que que se ejecuta en un hilo y que el hilo se suspende después de cualquiera de las líneas 1 o 2 y luego otro patadas de rosca en y se ejecuta:

b = 5 

Luego, cuando el otro hilo reanuda, b se sobrescribe con los viejos valores calculados , que probablemente no sea lo que se esperaba

Para que pueda ver que a pesar de que no se están ejecutando REALMENTE al mismo tiempo, todavía necesita bloqueo.

1

Todavía se necesitan bloqueos. Trataré de explicar por qué son necesarios.

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

Supongamos que hay dos hilos, digamos t1 y t2, y ambos quieren ejecutar dos instrucciones que leen el valor de una variable global y la incrementan.

#increment value 
global var 
read_var = var 
var = read_var + 1 

Como poner anteriormente, GIL solo garantiza que dos hilos no pueden ejecutar una instrucción a la vez, lo que significa que ambos hilos no pueden ejecutar read_var = var en cualquier instante particular de tiempo. Pero pueden ejecutar instrucciones una tras otra y aún así puede tener problemas. Considere esta situación:

  • READ_VAR Supongamos que es 0.
  • GIL está en manos de t1 hilo.
  • t1 ejecuta read_var = var. Entonces, read_var en t1 es 0. GIL solo se asegurará de que esta operación de lectura no se ejecute para ningún otro hilo en este instante.
  • GIL se asigna al hilo t2.
  • t2 ejecuta read_var = var. Pero read_var sigue siendo 0. Por lo tanto, read_var en t2 es 0.
  • GIL se otorga a t1.
  • t1 ejecuta var = read_var+1 y var se convierte en 1.
  • GIL se asigna a t2.
  • t2 piensa que read_var = 0, porque eso es lo que lee.
  • t2 ejecuta var = read_var+1 y var se convierte en 1.
  • Nuestra expectativa era que var debería convertirse en 2.
  • Por lo tanto, se debe usar un bloqueo para mantener tanto la lectura como el incremento como una operación atómica.
  • respuesta
  • Will Harris explica que a través de un ejemplo de código.
Cuestiones relacionadas