thread retardo poner hacer funcion esperar como python multithreading

retardo - thread sleep python



Comportamiento de Python''s time.sleep(0) en linux: ¿causa un cambio de contexto? (3)

Básicamente, está intentando usurpar el trabajo del programador de CPU del sistema operativo. Probablemente sería mucho mejor simplemente llamar a os.nice(100) para informar al programador que tiene una prioridad muy baja para que pueda hacer su trabajo correctamente.

Este patrón surge mucho pero no puedo encontrar una respuesta directa.

Un programa no crítico, no amigable podría hacer

while(True): # do some work

Usando otras tecnologías y plataformas, si desea permitir que este programa se ejecute en caliente (use la mayor cantidad de ciclos de CPU posible) pero sea cortés, permita que otros programas que se estén ejecutando para frenarme efectivamente, con frecuencia escriba:

while(True): #do some work time.sleep(0)

He leído información conflictiva sobre si este último enfoque haría lo que yo esperaría en python, ejecutándose en una caja de Linux. ¿Causa un cambio de contexto, lo que resulta en el comportamiento que mencioné anteriormente?

EDITAR: Para lo que vale la pena, probamos un pequeño experimento en Apple OSX (no teníamos a mano una caja de Linux). Esta caja tiene 4 núcleos más hyperthreading, por lo que desarrollamos 8 programas con solo un

while(True): i += 1

Como era de esperar, el Monitor de actividad muestra que cada uno de los 8 procesos consume más del 95% de la CPU (al parecer, con 4 núcleos e hiperprocesamiento, obtiene un total del 800%). Entonces hicimos un noveno programa. Ahora los 9 corren alrededor del 85%. Ahora mata al noveno chico y haz un programa con

while(True): i += 1 time.sleep(0)

Esperaba que este proceso usara cerca del 0% y los otros 8 funcionaran en un 95%. Pero en cambio, los nueve corren alrededor del 85%. Entonces, en Apple OSX, el sueño (0) parece no tener efecto.


Creo que ya tienes la respuesta de @Ninefingers, pero en esta respuesta intentaremos sumergirnos en el código fuente de Python.

Primero, el módulo de time Python se implementa en C y, para ver la implementación de la función time.sleep , puede consultar Modules/timemodule.c . Como puede ver (y sin entrar en todos los detalles específicos de la plataforma) esta función delegará la llamada a la función de floatsleep .

Ahora floatsleep está diseñado para funcionar en una plataforma diferente, pero aún así el comportamiento fue diseñado para ser similar siempre que sea posible, pero como solo estamos interesados ​​en una plataforma similar a Unix, verifiquemos que solo parte :

... Py_BEGIN_ALLOW_THREADS sleep((int)secs); Py_END_ALLOW_THREADS

Como puede ver, floatsleep está llamando a C sleep y desde la página de sleep man :

La función sleep () hará que el subproceso de llamada se suspenda de la ejecución hasta que haya transcurrido el número de segundos en tiempo real especificado por el argumento segundos o ...

Pero espera un minuto, ¿no nos olvidamos de la GIL?

Bueno, aquí es donde las macros Py_BEGIN_ALLOW_THREADS y Py_END_ALLOW_THREADS entraron en acción (marque Include/ceval.h si está interesado en la definición de estas dos macros), el código C anterior se puede traducir utilizando estas dos macros para:

Save the thread state in a local variable. Release the global interpreter lock. ... Do some blocking I/O operation ... (call sleep in our case) Reacquire the global interpreter lock. Restore the thread state from the local variable.

Se puede encontrar más información sobre esta macro dos en el documento c-api .

Espero que esto haya sido útil.


Nunca había pensado en esto, así que escribí este guión:

import time while True: print "loop" time.sleep(0.5)

Sólo como una prueba. Ejecutando esto con strace -o isacontextswitch.strace -s512 python test.py te da esta salida en el bucle:

write(1, "loop/n", 5) = 5 select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) write(1, "loop/n", 5) = 5 select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) write(1, "loop/n", 5) = 5 select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) write(1, "loop/n", 5) = 5 select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) write(1, "loop/n", 5)

select() es una llamada del sistema, así que sí, está cambiando de contexto (bueno, técnicamente, un cambio de contexto no es realmente necesario cuando se cambia al espacio del kernel, pero si tiene otros procesos en ejecución, lo que está diciendo aquí es que a menos que tenga los datos listos para leer en su descriptor de archivos, otros procesos pueden ejecutarse hasta entonces en el kernel para poder realizar esto. Curiosamente, el retraso es en la selección en la entrada estándar. Esto le permite a Python interrumpir su entrada en eventos como la entrada ctrl+c , si lo desean, sin tener que esperar a que el código se agote, lo que creo que es bastante claro.

Debo tener en cuenta que lo mismo se aplica a time.sleep(0) excepto que el parámetro de tiempo pasado es {0,0} . Y ese bloqueo de giro no es realmente ideal para cualquier cosa, sino demoras muy cortas: el multiprocessing y los threads proporcionan la capacidad de esperar en los objetos de eventos.

Edit : Así que eché un vistazo para ver exactamente qué hace Linux. La implementación en do_select ( fs/select.c ) hace esta comprobación:

if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { wait = NULL; timed_out = 1; } if (end_time && !timed_out) slack = select_estimate_accuracy(end_time);

En otras palabras, si se proporciona una hora de finalización y ambos parámetros son cero (! 0 = 1 y se evalúan como verdaderos en C), la espera se establece en NULL y la selección se considera agotada. Sin embargo, eso no significa que la función vuelva a ti; recorre todos los descriptores de archivos que tiene y llama a cond_resched , lo que potencialmente permite que se ejecute otro proceso. En otras palabras, lo que sucede depende completamente del programador; Si su proceso ha estado acaparando el tiempo de CPU en comparación con otros procesos, es probable que se produzca un cambio de contexto. De lo contrario, la tarea en la que se encuentra (la función kernel do_select) podría continuar hasta que se complete.

Sin embargo, me gustaría reiterar que la mejor manera de ser más amable con otros procesos generalmente consiste en utilizar otros mecanismos que no sea un bloqueo de giro.