¿Decidir entre subproceso, multiprocesamiento e hilo en Python?

110

Me gustaría paralelizar mi programa Python para que pueda hacer uso de múltiples procesadores en la máquina en la que se ejecuta. Mi paralelización es muy simple, ya que todos los "hilos" paralelos del programa son independientes y escriben su salida en archivos separados. No necesito los hilos para intercambiar información, pero es imperativo que sepa cuándo terminan los hilos, ya que algunos pasos de mi canalización dependen de su salida.

La portabilidad es importante, ya que me gustaría que se ejecutara en cualquier versión de Python en Mac, Linux y Windows. Dadas estas limitaciones, ¿cuál es el módulo de Python más apropiado para implementar esto? Estoy tratando de decidir entre subprocesos, subprocesos y multiprocesamiento, que parecen proporcionar una funcionalidad relacionada.

Tiene alguna idea sobre esto? Me gustaría la solución más simple que sea portátil.

Mula Vaibhav
fuente
Relacionado: stackoverflow.com/questions/1743293/… (lea mi respuesta allí para ver por qué los hilos no son un iniciador para el código Python puro)
1
"Cualquier versión de Python" es MUY vago. Python 2.3? 1.x? 3.x? Es simplemente una condición imposible de satisfacer.
detly

Respuestas:

64

multiprocessinges un gran módulo de tipo navaja suiza. Es más general que los subprocesos, ya que incluso puede realizar cálculos remotos. Por tanto, este es el módulo que le sugiero que utilice.

El subprocessmódulo también le permitiría iniciar múltiples procesos, pero encontré que era menos conveniente de usar que el nuevo módulo de multiprocesamiento.

Los subprocesos son notoriamente sutiles y, con CPython, a menudo está limitado a un núcleo, con ellos (aunque, como se indica en uno de los comentarios, el bloqueo de intérprete global (GIL) se puede lanzar en código C llamado desde código Python) .

Creo que la mayoría de las funciones de los tres módulos que cita se pueden utilizar de forma independiente de la plataforma. En el lado de la portabilidad, tenga en cuenta que multiprocessingsolo viene de serie desde Python 2.6 (aunque existe una versión para algunas versiones anteriores de Python). ¡Pero es un gran módulo!

Eric O Lebigot
fuente
1
para una asignación, utilicé el módulo "multiprocesamiento" y su método pool.map (). pedazo de pastel!
kmonsoor
¿También se está considerando algo como el apio? ¿Por qué lo es o no lo es?
user3245268
Por lo que puedo decir, Celery está más involucrado (tienes que instalar algún agente de mensajes), pero es una opción que probablemente debería tenerse en cuenta, dependiendo del problema en cuestión.
Eric O Lebigot
186

Para mí, esto es bastante simple:

La opción de subproceso :

subprocesses para ejecutar otros ejecutables --- es básicamente un contenedor os.fork()y os.execve()con algo de soporte para plomería opcional (configurando PIPE hacia y desde los subprocesos. Obviamente, podría otros mecanismos de comunicaciones entre procesos (IPC), como sockets, Posix o Memoria compartida SysV, pero estará limitado a las interfaces y canales IPC que sean compatibles con los programas que está llamando.

Comúnmente, uno usa cualquier subprocesssincrónico --- simplemente llamando a alguna utilidad externa y leyendo su salida o esperando su finalización (tal vez leyendo sus resultados de un archivo temporal, o después de que los haya publicado en alguna base de datos).

Sin embargo, uno puede generar cientos de subprocesos y sondearlos. Mi clase de utilidad favorita personal hace exactamente eso. La mayor desventaja del subprocessmódulo es que el soporte de E / S generalmente se bloquea. Hay un borrador PEP-3145 para arreglar eso en alguna versión futura de Python 3.xy un asyncproc alternativo (Advertencia que conduce directamente a la descarga, no a ningún tipo de documentación ni README). También descubrí que es relativamente fácil simplemente importar fcntly manipular los Popendescriptores de archivos PIPE directamente, aunque no sé si esto es portátil para plataformas que no son UNIX.

(Actualización: 7 de agosto de 2019: soporte de Python 3 para subprocesos ayncio : subprocesos asyncio )

subprocess casi no tiene soporte de manejo de eventos ... aunque puede usar el signalmódulo y señales simples de UNIX / Linux de la vieja escuela --- matando sus procesos suavemente, por así decirlo.

La opción de multiprocesamiento :

multiprocessinges para ejecutar funciones dentro de su código (Python) existente con soporte para comunicaciones más flexibles entre esta familia de procesos. En particular, es mejor construir su multiprocessingIPC alrededor de los Queueobjetos del módulo cuando sea posible, pero también puede usar Eventobjetos y varias otras características (algunas de las cuales, presumiblemente, están construidas alrededor del mmapsoporte en las plataformas donde ese soporte es suficiente).

El multiprocessingmódulo de Python está destinado a proporcionar interfaces y características que son muy similares a las threading que, al mismo tiempo, permiten a CPython escalar su procesamiento entre múltiples CPU / núcleos a pesar del GIL (bloqueo de intérprete global). Aprovecha todo el esfuerzo de coherencia y bloqueo SMP detallado que realizaron los desarrolladores del kernel de su sistema operativo.

La opción de enhebrado :

threadinges para una gama bastante estrecha de aplicaciones que están vinculadas a E / S (no es necesario escalar a través de múltiples núcleos de CPU) y que se benefician de la latencia extremadamente baja y la sobrecarga de conmutación de la conmutación de subprocesos (con memoria de núcleo compartida) frente al proceso / cambio de contexto. En Linux, este es casi el conjunto vacío (los tiempos de cambio de proceso de Linux están extremadamente cerca de sus cambios de hilo).

threadingsufre de dos grandes desventajas en Python .

Uno, por supuesto, es específico de la implementación, que afecta principalmente a CPython. Ese es el GIL. En su mayor parte, la mayoría de los programas CPython no se beneficiarán de la disponibilidad de más de dos CPU (núcleos) y, a menudo, el rendimiento se verá afectado por la contención de bloqueo de GIL.

El problema más importante, que no es específico de la implementación, es que los subprocesos comparten la misma memoria, controladores de señales, descriptores de archivos y otros recursos del sistema operativo. Por lo tanto, el programador debe tener mucho cuidado con el bloqueo de objetos, el manejo de excepciones y otros aspectos de su código que son sutiles y que pueden matar, detener o bloquear todo el proceso (conjunto de subprocesos).

En comparación, el multiprocessingmodelo le da a cada proceso su propia memoria, descriptores de archivo, etc. Una falla o una excepción no controlada en cualquiera de ellos solo matará ese recurso y manejar de manera robusta la desaparición de un proceso secundario o hermano puede ser considerablemente más fácil que depurar, aislar y solucionar o solucionar problemas similares en subprocesos.

  • (Nota: el uso de threadingcon los principales sistemas Python, como NumPy , puede sufrir considerablemente menos contención de GIL que la mayoría de su propio código Python. Eso es porque han sido diseñados específicamente para hacerlo; las partes nativas / binarias de NumPy, por ejemplo, lanzará el GIL cuando sea seguro).

La opción retorcida :

También vale la pena señalar que Twisted ofrece otra alternativa que es elegante y muy difícil de entender . Básicamente, a riesgo de simplificar demasiado hasta el punto en que los fanáticos de Twisted pueden asaltar mi hogar con horquillas y antorchas, Twisted ofrece multitarea cooperativa impulsada por eventos dentro de cualquier proceso (único).

Para comprender cómo esto es posible, uno debe leer acerca de las características de select()(que se pueden construir alrededor de select () o poll () o llamadas al sistema de sistema operativo similares). Básicamente, todo está impulsado por la capacidad de realizar una solicitud del sistema operativo para que se suspenda en espera de cualquier actividad en una lista de descriptores de archivos o algún tiempo de espera.

El despertar de cada una de estas llamadas a select()es un evento, ya sea uno que involucre entrada disponible (legible) en cierto número de sockets o descriptores de archivo, o espacio de almacenamiento disponible en algunos otros descriptores o sockets (escribibles), algunas condiciones excepcionales (TCP paquetes PUSH fuera de banda, por ejemplo), o un TIMEOUT.

Por lo tanto, el modelo de programación Twisted se basa en el manejo de estos eventos y luego en el controlador "principal" resultante, lo que le permite enviar los eventos a sus controladores.

Personalmente, creo que el nombre Twisted evoca el modelo de programación ... ya que su enfoque del problema debe ser, en cierto sentido, "retorcido" de adentro hacia afuera. En lugar de concebir su programa como una serie de operaciones sobre datos de entrada y salidas o resultados, está escribiendo su programa como un servicio o demonio y definiendo cómo reacciona a varios eventos. (De hecho, el núcleo "bucle principal" de un programa Twisted es (¿normalmente? ¿Siempre?) A reactor()).

Los principales desafíos para el uso de Twisted implican darle vueltas al modelo impulsado por eventos y también evitar el uso de bibliotecas de clases o kits de herramientas que no estén escritos para cooperar dentro del marco Twisted. Esta es la razón por la que Twisted proporciona sus propios módulos para el manejo de protocolos SSH, para curses y sus propias funciones de subproceso / Popen, y muchos otros módulos y manejadores de protocolos que, a primera vista, parecerían duplicar cosas en las bibliotecas estándar de Python.

Creo que es útil comprender Twisted a un nivel conceptual, incluso si nunca tienes la intención de usarlo. Puede brindar información sobre el rendimiento, la contención y el manejo de eventos en su subproceso, multiprocesamiento e incluso manejo de subprocesos, así como cualquier procesamiento distribuido que realice.

( Nota: Las nuevas versiones de Python 3.x están incluidos asyncio (E / S asíncrona) presenta como asíncrono def , el @ async.coroutine decorador, y el aguardan palabra clave y el rendimiento del futuro apoyo todos estos son más o menos similares a. Retorcido desde una perspectiva de proceso (cooperativa multitarea). (Para conocer el estado actual del soporte Twisted para Python 3, consulte: https://twistedmatrix.com/documents/current/core/howto/python3.html )

La opción distribuida :

Otro ámbito del procesamiento sobre el que no ha preguntado, pero que vale la pena considerar, es el del procesamiento distribuido . Existen muchas herramientas y marcos de Python para el procesamiento distribuido y el cálculo paralelo. Personalmente, creo que el más fácil de usar es el que menos se considera que está en ese espacio.

Es casi trivial construir un procesamiento distribuido alrededor de Redis . Todo el almacén de claves se puede usar para almacenar unidades de trabajo y resultados, las LIST de Redis se pueden usar como Queue()un objeto similar y el soporte PUB / SUB se puede usar para un Eventmanejo similar. Puede aplicar hash a sus claves y utilizar valores, replicados en un clúster suelto de instancias de Redis, para almacenar la topología y las asignaciones de tokens de hash para proporcionar hash y conmutación por error consistentes para escalar más allá de la capacidad de cualquier instancia única para coordinar a sus trabajadores y ordenación de datos (encurtidos, JSON, BSON o YAML) entre ellos.

Por supuesto, a medida que comienza a construir una solución a mayor escala y más sofisticada alrededor de Redis, está reimplementando muchas de las características que ya se han resuelto con Celery , Apache Spark y Hadoop , Zookeeper , etcd , Cassandra , etc. Todos ellos tienen módulos para que Python acceda a sus servicios.

[Actualización: un par de recursos para considerar si está considerando Python para uso intensivo de computación en sistemas distribuidos: IPython Parallel y PySpark . Si bien estos son sistemas informáticos distribuidos de propósito general, son subsistemas de ciencia y análisis de datos particularmente accesibles y populares].

Conclusión

Allí tiene la gama de alternativas de procesamiento para Python, desde un solo subproceso, con simples llamadas sincrónicas a subprocesos, grupos de subprocesos sondeados, subprocesos y multiprocesamiento, multitarea cooperativa impulsada por eventos y procesamiento distribuido.

Jim Dennis
fuente
1
Sin embargo, es difícil usar el multiprocesamiento con clases / OOP.
Tjorriemorrie
2
@Tjorriemorrie: Supongo que te refieres a que es difícil enviar llamadas a métodos a instancias de objetos que podrían estar en otros procesos. Sugeriría que este es el mismo problema que tendría con los hilos, pero más fácilmente visible (en lugar de ser frágil y estar sujeto a oscuras condiciones de carrera). Creo que el enfoque recomendado sería disponer que todo ese envío se produzca a través de objetos de cola, que funcionan con un solo subproceso, varios subprocesos y entre procesos. (Con alguna implementación de Redis o Celery Queue, incluso en un clúster de nodos)
Jim Dennis
2
Esta es una respuesta realmente buena. Ojalá estuviera en la introducción a la concurrencia en los documentos de Python3.
root-11
1
@ root-11 puede proponerlo a los mantenedores de documentos; Lo publiqué aquí para uso gratuito. Usted y ellos pueden usarlo, en su totalidad o en partes.
Jim Dennis
"Para mí, esto es bastante simple:" Me encanta. muchas gracias
Jerome
5

En un caso similar, opté por procesos separados y el poquito de comunicación necesaria a través del enchufe de red. Es altamente portátil y bastante simple de hacer usando Python, pero probablemente no sea más simple (en mi caso también tenía otra restricción: la comunicación con otros procesos escritos en C ++).

En su caso, probablemente optaría por el multiproceso, ya que los subprocesos de Python, al menos cuando se usa CPython, no son subprocesos reales. Bueno, son subprocesos nativos del sistema, pero los módulos C llamados desde Python pueden o no liberar el GIL y permitir que otros subprocesos se ejecuten al llamar al código de bloqueo.

kriss
fuente
4

Para utilizar varios procesadores en CPython, su única opción es el multiprocessingmódulo. CPython mantiene un bloqueo en sus componentes internos (el GIL ) que evita que los subprocesos en otros cpus funcionen en paralelo. El multiprocessingmódulo crea nuevos procesos (como subprocess) y gestiona la comunicación entre ellos.

Jochen Ritzel
fuente
5
Eso no es del todo cierto, AFAIK, puedes lanzar GIL usando la API de C, y hay otras implementaciones de Python como IronPython o Jython que no sufren tales limitaciones. Sin embargo, no voté en contra.
Bastien Léonard
1

Desplácese y deje que unix haga su trabajo:

use iterpipes para envolver el subproceso y luego:

Del sitio de Ted Ziuba

INPUTS_FROM_YOU | xargs -n1 -0 -P NUM ./process #NUM procesos paralelos

O

Gnu Parallel también servirá

Pasa el rato con GIL mientras envía a los chicos de la trastienda a hacer su trabajo multinúcleo.

chiggsy
fuente
6
"La portabilidad es importante, ya que me gustaría que se ejecutara en cualquier versión de Python en Mac, Linux y Windows".
detly
Con esta solución, ¿puede interactuar repetidamente con el trabajo? Puede hacer esto en multiprocesamiento, pero no lo creo en subproceso.
abalter