Roscado en Python

¿Cuáles son los módulos utilizados para escribir aplicaciones de múltiples subprocesos en Python? Soy consciente de los mecanismos básicos de concurrencia proporcionados por el lenguaje y también de Stackless Python , pero ¿cuáles son sus respectivas fortalezas y debilidades?

En orden de complejidad creciente:

Usa el módulo de enhebrado.

Pros:

  • Es realmente fácil ejecutar cualquier función (de hecho, cualquier llamable) en su propio hilo.
  • Compartir datos es, si no es fácil (los lockings nunca son fáciles :), al menos simple.

Contras:

  • Como lo mencionó Juergen, los hilos de Python no pueden acceder simultáneamente al estado en el intérprete (hay un gran locking, el infame Global Interpreter Lock ). Lo que eso significa en la práctica es que los hilos son útiles para las tareas de enlace de E / S (redes, escritura en disco, y así sucesivamente), pero no del todo útil para realizar cálculos concurrentes.

Utilizar el módulo de multiprocesamiento.

En el caso de uso simple, esto se ve exactamente como el uso de threading excepto que cada tarea se ejecuta en su propio proceso, no su propio hilo. (Casi literalmente: si toma el ejemplo de Eli y reemplaza el threading con multiprocessing , Thread , con Process y Queue (el módulo) con multiprocessing.Queue . multiprocessing.Queue , debería funcionar bien).

Pros:

  • Concurrencia real para todas las tareas (sin locking global de intérprete).
  • Las escalas a múltiples procesadores, incluso pueden escalar a múltiples máquinas .

Contras:

  • Los procesos son más lentos que los hilos.
  • El intercambio de datos entre procesos es más complicado que con subprocesos.
  • La memoria no se comparte implícitamente. O bien tienes que compartirlo explícitamente o debes escoger las variables y enviarlas de un lado a otro. Esto es más seguro, pero más difícil. (Si cada vez es más importante, los desarrolladores de Python parecen estar empujando a la gente en esta dirección).

Utilice un modelo de evento, como Twisted

Pros:

  • Usted obtiene un control extremadamente fino sobre la prioridad, sobre lo que se ejecuta cuando.

Contras:

  • Incluso con una buena biblioteca, la progtwigción asíncrona suele ser más difícil que la progtwigción de subprocesos, tanto en términos de comprensión de lo que se supone que sucede como de depuración de lo que realmente está sucediendo.

En todos los casos, asumo que ya comprende muchos de los problemas relacionados con la multitarea, específicamente el problema complicado de cómo compartir datos entre tareas. Si por alguna razón no sabe cuándo y cómo usar los candados y las condiciones, debe comenzar con ellos. El código de tareas múltiples está lleno de sutilezas y errores, y es mejor tener una buena comprensión de los conceptos antes de comenzar.

Ya ha recibido una gran variedad de respuestas, desde “subprocesos falsos” hasta marcos externos, pero no he visto a nadie mencionar Queue.Queue – la “salsa secreta” de subprocesos CPython.

Para expandir: siempre y cuando no necesite superponer el procesamiento pesado de la CPU de Python puro (en cuyo caso necesita multiprocessing , pero también viene con su propia implementación de Queue , por lo que puede aplicar las precauciones necesarias). consejo que estoy dando ;-), el threading incorporado de Python funcionará … pero lo hará mucho mejor si lo usas con precaución , por ejemplo, de la siguiente manera.

La memoria compartida “Olvídese”, supuestamente la principal ventaja de los procesos de subprocesos frente al multiprocesamiento: no funciona bien, no se escala bien, nunca lo ha hecho, nunca lo hará. Use la memoria compartida solo para las estructuras de datos que se configuran una vez antes de generar subprocesos y que nunca se cambian después, para todo lo demás, haga un solo hilo responsable de ese recurso y comuníquese con ese hilo a través de la Queue .

Dedique un subproceso especializado a todos los recursos que normalmente pensaría proteger mediante lockings: una estructura de datos mutable o un grupo cohesivo de los mismos, una conexión a un proceso externo (una base de datos, un servidor XMLRPC, etc.), un archivo externo, etc. Obtenga un pequeño grupo de subprocesos para tareas de propósito general que no tienen o no necesitan un recurso dedicado de ese tipo: no cree subprocesos cuando sea necesario, o la sobrecarga de cambio de subprocesos lo abrumará.

La comunicación entre dos subprocesos siempre es a través de Queue.Queue , una forma de paso de mensajes, la única base sana para el multiprocesamiento (además de la memoria transaccional, que es prometedora pero para la cual no conozco implementaciones dignas de producción, excepto In Haskell).

Cada subproceso dedicado que administra un solo recurso (o un pequeño conjunto cohesivo de recursos) escucha las solicitudes en una instancia específica de Queue.Queue. Los subprocesos de un grupo esperan en una única Cola.Queue compartida (la cola es segura para subprocesos y no le fallará en esto).

Los hilos que solo necesitan poner en cola una solicitud en alguna cola (compartida o dedicada) lo hacen sin esperar los resultados, y seguir adelante. Los hilos que eventualmente SÍ necesitan un resultado o confirmación para una solicitud ponen en cola un par (solicitud, recepción de cola) con una instancia de Queue.Queue que se acaba de realizar y, finalmente, cuando la respuesta o confirmación es indispensable para continuar, obtienen ) de su cola de recepción. Asegúrese de estar listo para obtener respuestas de error, así como respuestas reales o confirmaciones (los correos deferred de Twisted son excelentes para organizar este tipo de respuesta estructurada, ¡por cierto!).

También puede usar Queue para “estacionar” instancias de recursos que pueden ser usados ​​por cualquier subproceso, pero nunca pueden compartirse entre múltiples subprocesos a la vez (conexiones DB con algunos componentes DBAPI, cursores con otros, etc.) – esto le permite relajarse el requisito de subprocesos dedicados a favor de una mayor agrupación (un subproceso de agrupación que obtiene de la cola compartida una solicitud que necesita un recurso en una cola obtendrá ese recurso de la cola apropiada, esperando si es necesario, etc.).

Twisted es en realidad una buena forma de organizar este minueto (o la danza cuadrada, según sea el caso), no solo gracias a los aplazados, sino también por su architecture de base sólida, altamente escalable y sólida: puede organizar cosas para usar hilos o subprocesos solo cuando realmente garantizado, mientras que la mayoría de las cosas normalmente se consideran dignas de subprocesos en un solo subproceso controlado por evento.

Pero, me doy cuenta de que Twisted no es para todos: “dedique o agrupe recursos, use Cola en el wazoo, nunca haga nada que necesite un locking o, Guido no lo permita, cualquier procedimiento de sincronización aún más avanzado, como el semáforo o la condición”, el enfoque puede aún se puede usar incluso si no puede envolver su cabeza en torno a metodologías asíncronas basadas en eventos, y aún así ofrecerá más confiabilidad y rendimiento que cualquier otro enfoque de subprocesos ampliamente aplicable con el que me haya topado.

Depende de lo que esté intentando hacer, pero estoy parcial a solo usar el módulo de threading en la biblioteca estándar porque hace que sea realmente fácil tomar cualquier función y simplemente ejecutarlo en un subproceso separado.

 from threading import Thread def f(): ... def g(arg1, arg2, arg3=None): .... Thread(target=f).start() Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start() 

Y así. A menudo tengo una configuración de productor / consumidor utilizando una cola sincronizada provista por el módulo de Queue

 from Queue import Queue from threading import Thread q = Queue() def consumer(): while True: print sum(q.get()) def producer(data_source): for line in data_source: q.put( map(int, line.split()) ) Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start() for i in range(10): Thread(target=consumer).start() 

Kamaelia es un marco Python para crear aplicaciones con muchos procesos de comunicación.

(fuente: kamaelia.org ) Kamaelia – La concurrencia se hizo útil, divertida

En Kamaelia construyes sistemas a partir de componentes simples que se comunican entre sí . Esto acelera el desarrollo, ayuda de forma masiva al mantenimiento y también significa que usted construye software naturalmente concurrente . Está destinado a ser accesible por cualquier desarrollador, incluidos los principiantes. También lo hace divertido 🙂

¿Qué tipo de sistemas? Servidores de red, clientes, aplicaciones de escritorio, juegos basados ​​en pygame, sistemas de transencoding y tuberías, sistemas de TV digital, erradicadores de spam, herramientas de enseñanza y mucho más 🙂

Aquí hay un video de Pycon 2009. Comienza comparando Kamaelia con Twisted y Parallel Python y luego da una demostración práctica de Kamaelia.

Fácil concurrencia con Kamaelia – Parte 1 (59:08)
Fácil concurrencia con Kamaelia – Parte 2 (18:15)

Con respecto a Kamaelia, la respuesta anterior no cubre el beneficio aquí. El enfoque de Kamaelia proporciona una interfaz unificada, que es pragmática, no perfecta, para tratar los subprocesos, generadores y procesos en un solo sistema para la concurrencia.

Fundamentalmente, proporciona una metáfora de una cosa en ejecución que tiene bandejas de entrada y bandejas de salida. Envía mensajes a las bandejas de salida y, cuando están conectados entre sí, los mensajes fluyen de las bandejas de salida a las bandejas de entrada. Esta metáfora / API sigue siendo la misma, ya sea que esté utilizando generadores, subprocesos o procesos, o hablando con otros sistemas.

La parte “no es perfecta” se debe a que el azúcar sintáctica no se ha agregado todavía para las bandejas de entrada y salida (aunque esto se está discutiendo): hay un enfoque en la seguridad / usabilidad en el sistema.

Tomando el ejemplo del consumidor productor que usa hilos desnudos arriba, esto se convierte en esto en Kamaelia:

 Pipeline(Producer(), Consumer() ) 

En este ejemplo, no importa si estos son componentes de subprocesos o de lo contrario, la única diferencia entre ellos desde una perspectiva de uso es la clase de base para el componente. Los componentes del generador se comunican mediante listas, componentes de subprocesos que utilizan Queue.Queues y procesos basados ​​en os.pipes.

Sin embargo, la razón detrás de este enfoque es hacer que sea más difícil hacer que los errores sean difíciles de depurar. En la hebra, o en cualquier concurrencia de memoria compartida que tenga, el problema número uno que enfrenta es la ruptura accidental de las actualizaciones de datos compartidos. Al usar el paso de mensajes eliminas una clase de errores.

Si utiliza subprocesos simples y lockings en todas partes, generalmente está asumiendo que al escribir código no cometerá ningún error. Si bien todos aspiramos a eso, es muy raro que suceda. Al terminar el comportamiento de locking en un solo lugar, simplifica donde las cosas pueden salir mal. (Los controladores de contexto ayudan, pero no ayudan con actualizaciones accidentales fuera del controlador de contexto)

Obviamente, no todos los códigos pueden escribirse como mensajes de paso y estilos compartidos, por lo que Kamaelia también tiene un software simple de memoria transaccional (STM), que es una idea realmente clara con un nombre desagradable, es más como un control de versión para variables, es decir Echa un vistazo a algunas variables, actualízalas y confirma. Si tienes un choque te enjuagas y repites.

Enlaces relevantes:

  • Tutorial de Europython 09
  • Lanzamientos mensuales
  • Lista de correo
  • Ejemplos
  • Aplicaciones de ejemplo
  • Componentes reutilizables (generador e hilo)

De todos modos, espero que sea una respuesta útil. FWIW, la razón principal detrás de la configuración de Kamaelia es hacer que la concurrencia sea más segura y más fácil de usar en los sistemas python, sin que la cola mueva al perro. (es decir, el gran cubo de componentes

Puedo entender por qué la otra respuesta de Kamaelia se modificó, ya que incluso para mí se parece más a un anuncio que a una respuesta. Como autora de Kamaelia, es agradable ver entusiasmo, aunque espero que contenga un poco de contenido más relevante 🙂

Y esa es mi manera de decir, por favor tome la advertencia de que esta respuesta está sesgada por definición, pero para mí, el objective de Kamaelia es tratar de resumir lo que es la mejor práctica de la OMI. Yo sugeriría probar algunos sistemas y ver cuál funciona para usted. (también si esto no es apropiado para el desbordamiento de stack, lo siento, soy nuevo en este foro 🙂

Yo usaría los Microthreads (Tasklets) de Stackless Python, si tuviera que usar hilos en absoluto.

Todo un juego en línea (multijugador masivo) se basa en Stackless y su principio de multihilo, ya que el original es solo lento para la propiedad multijugador masivo del juego.

Los hilos en CPython están ampliamente desanimados. Una razón es el GIL, un locking de intérprete global, que serializa el subproceso para muchas partes de la ejecución. Mi experiencia es que es realmente difícil crear aplicaciones rápidas de esta manera. Mis codificaciones de ejemplo fueron todas más lentas con subprocesos, con un núcleo (pero muchas esperas de entrada deberían haber hecho posibles algunos aumentos de rendimiento).

Con CPython, use procesos separados si es posible.

Si realmente quiere ensuciarse las manos, puede intentar usar generadores para falsificar coroutines . Probablemente no sea el más eficiente en términos de trabajo involucrado, pero las rutinas sí le ofrecen un control muy fino de la multitarea cooperativa en lugar de la multitarea preventiva que encontrará en otros lugares.

Una de las ventajas que encontrará es que, en general, no necesitará lockings ni mutexiones cuando use la multitarea cooperativa, pero la ventaja más importante para mí fue la velocidad de conmutación casi cero entre “hilos”. Por supuesto, se dice que Stackless Python también es muy bueno para eso; y luego está Erlang, si no tiene que ser Python.

Probablemente la mayor desventaja de la multitarea cooperativa es la falta general de soluciones para bloquear la E / S. Y en las rutinas falsas, también encontrarás el problema de que no puedes cambiar los “hilos” desde cualquier cosa que no sea el nivel superior de la stack dentro de un hilo.

Una vez que haya creado una aplicación incluso ligeramente compleja con coroutines falsos, realmente empezará a apreciar el trabajo que implica la progtwigción de procesos a nivel del sistema operativo.