Para mí, esto es bastante simple:
La opción de subproceso :
subprocess
es 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 subprocess
sincró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 subprocess
mó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 fcntl
y manipular los Popen
descriptores 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 signal
módulo y señales simples de UNIX / Linux de la vieja escuela --- matando sus procesos suavemente, por así decirlo.
La opción de multiprocesamiento :
multiprocessing
es 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 multiprocessing
IPC alrededor de los Queue
objetos del módulo cuando sea posible, pero también puede usar Event
objetos y varias otras características (algunas de las cuales, presumiblemente, están construidas alrededor del mmap
soporte en las plataformas donde ese soporte es suficiente).
El multiprocessing
mó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 :
threading
es 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).
threading
sufre 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 multiprocessing
modelo 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
threading
con 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 Event
manejo 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.