Mejor solución para Python Threading.Event espera de ocupado

Estoy usando Threading.Event bastante estándar: el hilo principal llega a un punto en el que se encuentra en un bucle que se ejecuta:

event.wait(60) 

Los otros bloques en una solicitud hasta que una respuesta esté disponible y luego inicia un:

 event.set() 

Espero que el hilo principal se seleccione durante 40 segundos, pero este no es el caso. Desde la fuente de Python 2.7 Lib / threading.py:

 # Balancing act: We can't afford a pure busy loop, so we # have to sleep; but if we sleep the whole timeout time, # we'll be unresponsive. The scheme here sleeps very # little at first, longer as time goes on, but never longer # than 20 times per second (or the timeout time remaining). endtime = _time() + timeout delay = 0.0005 # 500 us -> initial delay of 1 ms while True: gotit = waiter.acquire(0) if gotit: break remaining = endtime - _time() if remaining <= 0: break delay = min(delay * 2, remaining, .05) _sleep(delay) 

Lo que obtenemos es un selecto syscall run cada 500us. Esto causa una carga notable en la máquina con un bucle de selección bastante ajustado.

¿Puede alguien explicar por qué hay un acto de equilibrio involucrado y por qué es diferente a un hilo que espera en un descriptor de archivo?

    y segundo, ¿existe una mejor manera de implementar un hilo principal mayormente inactivo sin un bucle tan cerrado?

    Recientemente tuve el mismo problema y también lo localicé en este bloque de código exacto en el módulo de threading .

    Apesta

    La solución sería sobrecargar el módulo de subprocesamiento o migrar a python3 , donde esta parte de la implementación se ha corregido.

    En mi caso, migrar a python3 hubiera sido un gran esfuerzo, así que elegí el primero. Lo que hice fue:

    1. cython un archivo cython rápido (usando cython ) con una interfaz para pthread . Incluye funciones de python que invocan las funciones pthread_mutex_* correspondientes, y enlaces contra libpthread . Específicamente, la función más relevante para la tarea que nos interesa es pthread_mutex_timedlock .
    2. Creé un nuevo módulo threading2 (y reemplacé todas las líneas de import threading en mi base de código con import threading2 ). En threading2 , threading2 todas las clases relevantes de threading ( Lock , Condition , Event ) y también las de Queue que uso mucho ( Queue y PriorityQueue ). La clase de Lock se volvió a implementar completamente con las funciones pthread_mutex_* , pero el rest fue mucho más fácil: simplemente hice una subclase del original (por ejemplo, threading.Event ), y sobrescribí __init__ para crear mi nuevo tipo de Lock . El rest acaba de funcionar.

    La implementación del nuevo tipo de Lock fue muy similar a la implementación original en los threading , pero python3 la nueva implementación de la acquire en el código que encontré en el módulo de threading python3 (que, naturalmente, es mucho más simple que el mencionado “acto de equilibrio” “bloque). Esta parte fue bastante fácil.

    (Por cierto, el resultado en mi caso fue un 30% de aceleración de mi proceso de múltiples subprocesos masivos. Incluso más de lo que esperaba).

    Estoy totalmente de acuerdo contigo, esto es escaso.

    Actualmente, me quedo con una simple llamada de selección, sin tiempo de espera, y escuchando en una tubería creada anteriormente. La activación se realiza mediante la escritura de un personaje en la tubería.

    Ver estas funciones de sueño y activación de Gunicorn.