2012-03-14 7 views
7

estoy usando Threading.Event bastante estándar: hilo principal llega a un punto donde su en un bucle que se ejecuta:mejor solución para Python Threading.Event semi-ocupado esperando

event.wait(60) 

Los otros bloques de una solicitud hasta que una respuesta se encuentra disponible y luego inicia una:

event.set() 

yo esperaría que el hilo principal para seleccionar 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 pasan cada 500us. Esto causa una carga notable en la máquina con un lazo de selección bastante apretado.

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

y segundo, ¿hay una mejor manera de implementar un hilo principal que duerme en su mayoría sin un lazo tan apretado?

Respuesta

3

Recientemente me tocó el mismo problema, y ​​también lo rastreé hasta este bloque exacto de código en el módulo threading.

Apesta.

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

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

  1. he creado un archivo de .so rápida (usando cython) con una interfaz para pthread. Incluye funciones de python que invocan las funciones correspondientes pthread_mutex_*, 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 import threading en mi base de código con import threading2). En threading2, que redefinen todas las clases pertinentes de threading (Lock, Condition, Event), y también los de Queue que yo uso mucho (Queue y PriorityQueue). La clase Lock se reimplementó completamente utilizando las funciones pthread_mutex_*, pero el resto fue mucho más fácil: simplemente subclasé el original (por ejemplo, threading.Event) y anulé __init__ para crear mi nuevo tipo Lock. El resto solo funcionó.

La aplicación del nuevo tipo Lock era muy similar a la aplicación original en threading, pero basa el nuevo implemenation de acquire en el código que encontré en threading módulo python3 's (que, naturalmente, es mucho más sencillo que el bloque "acto de equilibrio" antes mencionado). Esta parte fue bastante fácil.

(Por cierto, el resultado en mi caso fue 30% de aceleración de mi proceso de subprocesamiento masivo. Incluso más de lo que esperaba.)

2

Estoy totalmente de acuerdo contigo, esto es cojo.

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 escribiendo un carácter en la tubería.

Ver esto sleep y wakeup funciones de gunicorn.

Cuestiones relacionadas