Subprocesos en una aplicación PyQt: ¿Usar hilos Qt o hilos Python?

Estoy escribiendo una aplicación GUI que regularmente recupera datos a través de una conexión web. Dado que esta recuperación tarda un tiempo, esto hace que la IU no responda durante el proceso de recuperación (no se puede dividir en partes más pequeñas). Por eso me gustaría externalizar la conexión web a un subproceso de trabajo separado.

[Sí, lo sé, ahora tengo dos problemas .]

De todos modos, la aplicación usa PyQt4, así que me gustaría saber cuál es la mejor opción: ¿Usar los hilos de Qt o el módulo de threading Python? ¿Cuáles son las ventajas / desventajas de cada uno? ¿O tienes una sugerencia totalmente diferente?

Editar (recompensar): Si bien la solución en mi caso particular probablemente será utilizar una solicitud de red sin locking como sugirieron Jeff Ober y Lukáš Lalinský (así que básicamente dejar los problemas de concurrencia a la implementación de redes), todavía me gustaría más respuesta en profundidad a la pregunta general:

¿Cuáles son las ventajas y desventajas de usar subprocesos de PyQt4 (es decir, Qt) sobre subprocesos nativos de Python (del módulo de threading )?


Edit 2: Gracias a todos por sus respuestas. Aunque no hay un acuerdo del 100%, parece haber un consenso generalizado de que la respuesta es “usar Qt”, ya que la ventaja de esto es la integración con el rest de la biblioteca, sin causar desventajas reales.

Para cualquier persona que quiera elegir entre las dos implementaciones de subprocesos, les recomiendo que lean todas las respuestas proporcionadas aquí, incluido el subproceso de la lista de correo PyQt al que abad se vincula.

Hubo varias respuestas que consideré para la recompensa; al final elegí abad para la referencia externa muy relevante; Fue, sin embargo, una llamada cercana.

Gracias de nuevo.

Esto fue discutido no hace mucho tiempo en la lista de correo PyQt. Citando los comentarios de Giovanni Bajo sobre el tema:

Es sobre todo lo mismo. La principal diferencia es que los QThreads están mejor integrados con Qt (señales / ranuras asíncronas, bucle de eventos, etc.). Además, no puede usar Qt desde un hilo de Python (no puede, por ejemplo, publicar un evento en el hilo principal a través de QApplication.postEvent): necesita un QThread para que funcione.

Una regla general podría ser usar QThreads si vas a interactuar de alguna manera con Qt, y usar los hilos de Python de lo contrario.

Y algunos comentarios anteriores sobre este tema del autor de PyQt: “ambos son envoltorios alrededor de las mismas implementaciones de subprocesos nativos”. Y ambas implementaciones usan GIL de la misma manera.

Los subprocesos de Python serán más simples y seguros, y como es para una aplicación basada en E / S, pueden omitir GIL. Dicho esto, ¿ha considerado la E / S sin locking utilizando sockets / selectos retorcidos o sin locking?

EDITAR: más en hilos

Hilos de python

Los hilos de Python son hilos de sistema. Sin embargo, Python utiliza un locking de intérprete global (GIL) para garantizar que el intérprete solo ejecute un bloque de cierto tamaño de instrucciones de código de bytes a la vez. Afortunadamente, Python libera la GIL durante las operaciones de entrada / salida, lo que hace que los subprocesos sean útiles para simular E / S no bloqueantes.

Importante advertencia: esto puede ser engañoso, ya que el número de instrucciones de código de bytes no corresponde al número de líneas en un progtwig. Incluso una sola asignación puede no ser atómica en Python, por lo que es necesario un locking de exclusión mutua para cualquier bloque de código que deba ejecutarse de forma atómica, incluso con la GIL.

Hilos QT

Cuando Python entrega el control a un módulo comstackdo por un tercero, libera la GIL. Se convierte en responsabilidad del módulo garantizar la atomicidad cuando sea necesario. Cuando el control se devuelve, Python usará el GIL. Esto puede hacer que el uso de bibliotecas de terceros en conjunto con hilos sea confuso. Es aún más difícil usar una biblioteca de subprocesos externa porque agrega incertidumbre sobre dónde y cuándo está el control en las manos del módulo frente al intérprete.

Los hilos QT operan con el GIL liberado. Los subprocesos QT pueden ejecutar código de biblioteca QT (y otro código de módulo comstackdo que no adquiera el GIL) al mismo tiempo. Sin embargo, el código Python ejecutado dentro del contexto de un subproceso QT aún adquiere el GIL, y ahora tiene que administrar dos conjuntos de lógica para bloquear su código.

Al final, tanto los hilos QT como los hilos Python son envoltorios alrededor de los hilos del sistema. Los hilos de Python son marginalmente más seguros de usar, ya que las partes que no están escritas en Python (usando implícitamente el GIL) usan el GIL en cualquier caso (aunque la advertencia anterior sigue siendo válida).

E / S sin locking

Los hilos agregan una complejidad extraordinaria a tu aplicación. Especialmente cuando se trata de la interacción ya compleja entre el intérprete de Python y el código del módulo comstackdo. Si bien muchos encuentran que la progtwigción basada en eventos es difícil de seguir, la E / S sin locking basada en eventos es a menudo mucho menos difícil de razonar que las hebras.

Con la E / S asíncrona, siempre puede estar seguro de que, para cada descriptor abierto, la ruta de ejecución es coherente y ordenada. Obviamente, hay problemas que deben abordarse, como qué hacer cuando el código que depende de un canal abierto depende de los resultados del código que se debe llamar cuando otro canal abierto devuelve datos.

Una buena solución para la E / S no bloqueante basada en eventos es la nueva biblioteca Diesel . Actualmente está restringido a Linux, pero es extraordinariamente rápido y bastante elegante.

También vale la pena su tiempo para aprender pyevent , un envoltorio alrededor de la maravillosa biblioteca libevent, que proporciona un marco básico para la progtwigción basada en eventos utilizando el método más rápido disponible para su sistema (determinado en el momento de la comstackción).

La ventaja de QThread es que está integrado con el rest de la biblioteca Qt. Es decir, los métodos compatibles con subprocesos en Qt deberán saber en qué subprocesos se ejecutan, y para mover objetos entre subprocesos, deberá usar QThread . Otra característica útil es ejecutar su propio bucle de eventos en un hilo.

Si está accediendo a un servidor HTTP, debe considerar QNetworkAccessManager .

Me hice la misma pregunta cuando trabajaba para PyTalk .

Si está usando Qt, necesita usar QThread para poder usar el marco Qt y especialmente el sistema de señal / ranura.

Con el motor de señal / ranura, podrá hablar de un hilo a otro y con cada parte de su proyecto.

Además, no hay una pregunta de rendimiento sobre esta elección ya que ambos son enlaces de C ++.

Aquí está mi experiencia de PyQt y el hilo.

Te animo a que uses QThread .

Jeff tiene algunos puntos buenos. Solo un hilo principal puede hacer actualizaciones de GUI. Si necesita actualizar la GUI desde el hilo, las señales de conexión en cola de Qt-4 facilitan el envío de datos a través de hilos y se invocarán automáticamente si está usando QThread; No estoy seguro si lo estarán si estás usando hilos de Python, aunque es fácil agregar un parámetro para connect() .

Realmente tampoco puedo recomendarlo, pero puedo intentar describir las diferencias entre los hilos CPython y Qt.

En primer lugar, los hilos de CPython no se ejecutan simultáneamente, al menos no el código de Python. Sí, crean subprocesos del sistema para cada subproceso de Python, sin embargo, solo se permite la ejecución del subproceso global que actualmente tiene el Bloqueo de Intérprete Global (las extensiones C y el código FFI pueden omitirlo, pero el código de bytes de Python no se ejecuta mientras el subproceso no contiene GIL).

Por otro lado, tenemos subprocesos Qt, que son básicamente capas comunes sobre los subprocesos del sistema, no tienen Bloqueo global de intérpretes y, por lo tanto, son capaces de ejecutarse simultáneamente. Sin embargo, no estoy seguro de cómo lo maneja PyQt, a menos que sus subprocesos Qt llamen al código Python, deberían poder ejecutarse simultáneamente (excluyendo varios lockings adicionales que podrían implementarse en varias estructuras).

Para un ajuste fino adicional, puede modificar la cantidad de instrucciones de bytecode que se interpretan antes de cambiar la propiedad de GIL; valores más bajos significan más cambio de contexto (y posiblemente una mayor capacidad de respuesta) pero menor rendimiento por hilo individual (los cambios de contexto tienen su costo, si intente cambiar cada pocas instrucciones, no ayuda a la velocidad.)

Espero que te ayude con tus problemas 🙂

No puedo comentar las diferencias exactas entre los subprocesos de Python y PyQt, pero he estado haciendo lo que intentas hacer usando QThread , QNetworkAcessManager y asegurándome de llamar a QApplication.processEvents() mientras el subproceso está vivo. Si la capacidad de respuesta de la GUI es realmente el problema que estás tratando de resolver, esto te ayudará.