¿Cómo funcionan los subprocesos en Python y cuáles son los errores específicos comunes de los subprocesos de Python?

85

He estado tratando de entender cómo funcionan los subprocesos en Python, y es difícil encontrar buena información sobre cómo funcionan. Puede que me falte un enlace o algo así, pero parece que la documentación oficial no es muy completa sobre el tema y no he podido encontrar una buena reseña.

Por lo que puedo decir, solo se puede ejecutar un hilo a la vez, y el hilo activo cambia cada 10 instrucciones aproximadamente.

¿Dónde hay una buena explicación o puedes dar una? También sería muy bueno estar al tanto de los problemas comunes con los que se encuentra al usar hilos con Python.

jdd
fuente

Respuestas:

50

Sí, debido al bloqueo de intérprete global (GIL), solo se puede ejecutar un hilo a la vez. Aquí hay algunos enlaces con algunas ideas sobre esto:

Desde el último enlace una cita interesante:

Déjame explicarte qué significa todo eso. Los subprocesos se ejecutan dentro de la misma máquina virtual y, por lo tanto, se ejecutan en la misma máquina física. Los procesos pueden ejecutarse en la misma máquina física o en otra máquina física. Si diseña su aplicación alrededor de subprocesos, no ha hecho nada para acceder a varias máquinas. Por lo tanto, puede escalar a tantos núcleos en una sola máquina (que serán bastantes con el tiempo), pero para alcanzar realmente las escalas web, deberá resolver el problema de múltiples máquinas de todos modos.

Si desea utilizar varios núcleos, pyprocessing define una API basada en procesos para realizar una paralelización real. El PEP también incluye algunos puntos de referencia interesantes.

Peter Hoffmann
fuente
1
Realmente un comentario sobre la cita de smoothspan: seguramente el subproceso de Python lo limita efectivamente a un núcleo, incluso si la máquina tiene varios. Puede haber beneficios de multinúcleo ya que el siguiente hilo puede estar listo para funcionar sin un cambio de contexto, pero sus hilos de Python nunca pueden hacer uso de> 1 núcleo a la vez.
James Brady
2
Correcto, los subprocesos de Python están prácticamente limitados a un núcleo, A MENOS QUE un módulo C interactúe bien con el GIL y ejecute su propio subproceso nativo.
Arafangion
En realidad, múltiples núcleos hacen que los subprocesos sean menos eficientes, ya que hay mucha rotación al verificar si cada subproceso puede acceder al GIL. Incluso con el nuevo GIL, el rendimiento es aún peor ... dabeaz.com/python/NewGIL.pdf
Básico
2
Tenga en cuenta que las consideraciones de GIL no se aplican a todos los intérpretes. Hasta donde yo sé, tanto IronPython como Jython funcionan sin un GIL, lo que permite que su código haga un uso más efectivo del hardware multiprocesador. Como mencionó Arafangion, el intérprete de CPython también puede ejecutar correctamente varios subprocesos si el código que no necesita acceso a los elementos de datos de Python libera el bloqueo y luego lo vuelve a adquirir antes de regresar.
holdenweb
¿Qué causa un cambio de contexto entre los hilos en Python? ¿Se basa en interrupciones del temporizador? ¿Bloqueo o una llamada de rendimiento específica?
CMCDragonkai
36

Python es un lenguaje bastante fácil de enhebrar, pero hay salvedades. Lo más importante que debe conocer es el bloqueo de intérprete global. Esto permite que solo un hilo acceda al intérprete. Esto significa dos cosas: 1) rara vez se encuentra usando una declaración de bloqueo en Python y 2) si desea aprovechar los sistemas multiprocesador, debe usar procesos separados. EDITAR: También debo señalar que puede poner parte del código en C / C ++ si también desea sortear el GIL.

Por lo tanto, debe volver a considerar por qué desea utilizar hilos. Si desea paralelizar su aplicación para aprovechar la arquitectura de doble núcleo, debe considerar dividir su aplicación en varios procesos.

Si desea mejorar la capacidad de respuesta, debe CONSIDERAR el uso de subprocesos. Sin embargo, existen otras alternativas, a saber, los microhilos . También hay algunos marcos que debería considerar:

Jason Baker
fuente
@JS - Fijo. De todos modos, esa lista estaba desactualizada.
Jason Baker
Simplemente me parece mal que necesite múltiples procesos, con toda la sobrecarga que ello implica, para aprovechar un sistema de múltiples núcleos. Tenemos algunos servidores con 32 núcleos lógicos, ¿entonces necesito 32 procesos para usarlos de manera eficiente? Madness
Básico
@Basic: la sobrecarga de iniciar un proceso frente a iniciar un hilo en estos días es mínima. Supongo que puede comenzar a ver problemas si estamos hablando de miles de consultas por segundo, pero luego cuestionaría la elección de Python para un servicio tan ocupado en primer lugar.
Jason Baker
20

A continuación se muestra una muestra básica de subprocesos. Generará 20 hilos; cada hilo generará su número de hilo. Ejecútelo y observe el orden en que se imprimen.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Como ha insinuado, los subprocesos de Python se implementan a través del tiempo. Así es como obtienen el efecto "paralelo".

En mi ejemplo, mi clase Foo extiende el hilo, luego implemento el runmétodo, que es donde va el código que le gustaría ejecutar en un hilo. Para iniciar el hilo, llame start()al objeto hilo, que invocará automáticamente el runmétodo ...

Por supuesto, esto es solo lo básico. Eventualmente querrá aprender sobre semáforos, mutex y bloqueos para la sincronización de hilos y el paso de mensajes.

mmattax
fuente
10

Use subprocesos en Python si los trabajadores individuales están realizando operaciones vinculadas de E / S. Si está intentando escalar a través de múltiples núcleos en una máquina, busque un buen marco IPC para Python o elija un idioma diferente.

Ben McNiel
fuente
4

Nota: donde sea que mencione threadme refiero específicamente a hilos en Python hasta que se indique explícitamente.

Los subprocesos funcionan de manera un poco diferente en Python si proviene de un C/C++segundo plano. En Python, solo un subproceso puede estar en estado de ejecución en un momento dado, lo que significa que los subprocesos en Python no pueden aprovechar realmente el poder de varios núcleos de procesamiento, ya que, por diseño, no es posible que los subprocesos se ejecuten en paralelo en varios núcleos.

Como la administración de memoria en Python no es segura para subprocesos, cada subproceso requiere un acceso exclusivo a las estructuras de datos en el intérprete de Python. Este acceso exclusivo se adquiere mediante un mecanismo llamado (bloqueo de interpretación global) .GIL

Why does python use GIL?

Para evitar que varios subprocesos accedan al estado del intérprete simultáneamente y corrompan el estado del intérprete.

La idea es que cada vez que se está ejecutando un hilo (incluso si es el hilo principal) , se adquiere un GIL y, después de un intervalo de tiempo predefinido, el hilo actual libera el GIL y otro hilo lo vuelve a adquirir (si lo hay).

Why not simply remove GIL?

No es que sea imposible eliminar GIL, es solo que, en el intento de hacerlo, terminamos colocando múltiples bloqueos dentro del intérprete para serializar el acceso, lo que hace que incluso una aplicación de un solo subproceso tenga menos rendimiento.

por lo que el costo de eliminar GIL se compensa con un rendimiento reducido de una aplicación de un solo hilo, lo que nunca se desea.

So when does thread switching occurs in python?

El cambio de hilo ocurre cuando se libera GIL, entonces, ¿cuándo se libera GIL? Hay dos escenarios a tener en cuenta.

Si un subproceso está realizando operaciones vinculadas a la CPU (procesamiento de imágenes Ex).

En las versiones anteriores de Python, el cambio de subproceso solía ocurrir después de un número fijo de instrucciones de Python. Estaba configurado de forma predeterminada en 100. Resultó que no es una política muy buena para decidir cuándo debe ocurrir el cambio, ya que el tiempo dedicado a ejecutar una sola instrucción puede variar de milisegundos a incluso un segundo. Por lo tanto, liberar GIL después de cada 100instrucción, independientemente del tiempo que tarden en ejecutarse, es una política deficiente.

En las nuevas versiones, en lugar de utilizar el recuento de instrucciones como métrica para cambiar de hilo, se utiliza un intervalo de tiempo configurable. El intervalo de cambio predeterminado es de 5 milisegundos. Puede obtener el intervalo de cambio actual utilizando sys.getswitchinterval(). Esto se puede modificar usandosys.setswitchinterval()

Si un subproceso está realizando algunas operaciones IO enlazadas (acceso al sistema de archivos Ex o
IO de red)

GIL se libera siempre que el hilo está esperando que se complete la operación de E / S.

Which thread to switch to next?

El intérprete no tiene su propio programador. Qué hilo se programa al final del intervalo es decisión del sistema operativo. .

anekix
fuente
3

Una solución sencilla para el GIL es el módulo de multiprocesamiento . Se puede usar como reemplazo directo del módulo de subprocesos, pero usa múltiples procesos de intérprete en lugar de subprocesos. Debido a esto, hay un poco más de sobrecarga que el simple subproceso para cosas simples, pero le brinda la ventaja de la paralelización real si la necesita. También se escala fácilmente a múltiples máquinas físicas.

Si necesita una paralelización a gran escala, buscaría más, pero si solo desea escalar a todos los núcleos de una computadora o algunas diferentes sin todo el trabajo que implica implementar un marco más completo, entonces esto es para usted. .

voluntad
fuente
2

Trate de recordar que el GIL está configurado para sondear de vez en cuando para mostrar la apariencia de múltiples tareas. Esta configuración se puede ajustar con precisión, pero ofrezco la sugerencia de que debería haber trabajo que los subprocesos estén haciendo o que muchos cambios de contexto causarán problemas.

Me atrevería a sugerir varios padres en los procesadores y trataría de mantener trabajos similares en el mismo núcleo (s).

phreaki
fuente