2010-10-07 10 views
9

que tienen una página ASP.NET con este código pseduo:¿Por qué Thread.Sleep() es tan intensivo en la CPU?

while (read) 
{ 
    Response.OutputStream.Write(buffer, 0, buffer.Length); 
    Response.Flush(); 
} 

Cualquier cliente que solicita esta página comenzará a descargar un archivo binario. Todo está bien en este momento, pero los clientes no tenían límite de velocidad de descarga, por lo que cambió el código anterior para esto:

while (read) 
{ 
    Response.OutputStream.Write(buffer, 0, buffer.Length); 
    Response.Flush(); 
    Thread.Sleep(500); 
} 

problema de la velocidad se resuelve ahora, pero bajo prueba con 100 clientes simultáneos que se conectan una tras otra (3 segundos retraso entre cada nueva conexión) el uso de la CPU aumenta cuando aumenta el número de clientes y cuando hay entre 70 y 80 clientes simultáneos, la CPU alcanza el 100% y se rechaza cualquier conexión nueva. Los números pueden ser diferentes en otras máquinas pero la pregunta es ¿por qué Thread.Sleep() es tan intensivo en la CPU y hay alguna forma de acelerar el cliente sin que la CPU se eleve?

Puedo hacerlo a nivel IIS pero necesito más control desde el interior de mi aplicación.

+0

Seguramente no se trata de la cantidad de clientes concurrentes ni del momento en que se conectan y reciben datos porque las mismas pruebas se realizan con el mismo número de clientes e incluso a mayor velocidad de descarga (sin límite como Thread.Sleep) y probando durante mucho tiempo y la CPU era <5%, por lo que no se trata de la cantidad de subprocesos disponibles en el grupo de subprocesos de ASP.NET y el tiempo que están vinculados a las solicitudes. Es Thread. Sleep, pero tal vez signifique algo diferente en mi escenario. – Xaqron

+0

¿Cómo lo hace eso no sobre el número de subprocesos? Está manteniendo el mismo número de clientes cuando no está durmiendo, por lo que se usarán menos hilos. –

+0

He hecho eso (mantener la cantidad de clientes igual) con mucho trabajo que simplemente dejarlos dormir pero no tengo CPU subida entonces! Entonces, ¿cómo descansar es más intenso que trabajar? Supongo (después de la revisión de las respuestas de Michael y Jon) que se trata de la forma en que ASP.NET automatiza el uso de subprocesos (supongo que Jon hizo la prueba en la consola no en asp.net) y cuando mi aplicación no duerme, cambia los hilos, pero cuando usas Sleep no los respaldas en la piscina (¡solo una conjetura como la de Michael)! – Xaqron

Respuesta

29

Sólo una conjetura:

no creo que es Thread.Sleep() que está atando la CPU - es el hecho de que usted está haciendo que los hilos por atar responder a una solicitud de tanto tiempo, y las necesidades del sistema para activar nuevos subprocesos (y otros recursos) para responder a las nuevas solicitudes, ya que esos subprocesos de espera ya no están disponibles en el grupo de subprocesos.

+1

+1 Spinning up newreads es/muy/costoso, y es muy fácil obtener la falta de hilo en IIS – FacticiusVir

+0

Repetí la prueba con 100 clientes sin límite de velocidad de descarga. Estoy seguro de que la única diferencia entre el código que no aumenta la CPU y el que lo hace es solo Thread.Sleep(). Tengo pruebas para tiempos más largos con más clientes y la CPU estaba en 1 ~ 2% todo el tiempo. – Xaqron

+0

@ FacticiusVir: No giro. Yo uso 'Dormir'. Entonces, el subproceso de trabajo ASP.NET libera la CPU. Utilizando la configuración de IIS, es posible establecer un límite de velocidad de conexión que no es mi elección aquí. – Xaqron

2

En lugar de una página ASP.NET debe implementar un IHttpAsyncHandler. El código de página ASP.NET pone muchas cosas entre su código y el navegador que no serían apropiadas para transferir archivos binarios. Además, dado que está intentando realizar una limitación de velocidad, debe utilizar un código asíncrono para limitar el uso de recursos, lo que sería difícil en una página ASP.NET. Crear un IHttpAsyncHandler es bastante simple. Simplemente active algunas operaciones asincrónicas en el método BeginProcessRequest, y no olvide cerrar correctamente el contexto para mostrar que ha llegado al final del archivo. IIS no podrá cerrarlo aquí.

El siguiente es mi ejemplo realmente malo de cómo realizar una operación asincrónica que consta de una serie de pasos, que van del 0 al 10, cada uno realizado en un intervalo de 500 ms.

using System; 
using System.Threading; 

namespace ConsoleApplication1 { 
    class Program { 
     static void Main() { 
      // Create IO instances 
      EventWaitHandle WaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); // We don't actually fire this event, just need a ref 
      EventWaitHandle StopWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); 
      int Counter = 0; 
      WaitOrTimerCallback AsyncIOMethod = (s, t) => { }; 
      AsyncIOMethod = (s, t) => { 
       // Handle IO step 
       Counter++; 
       Console.WriteLine(Counter); 
       if (Counter >= 10) 
        // Counter has reaced 10 so we stop 
        StopWaitHandle.Set(); 
       else 
        // Register the next step in the thread pool 
        ThreadPool.RegisterWaitForSingleObject(WaitHandle, AsyncIOMethod, null, 500, true); 
      }; 

      // Do initial IO 
      Console.WriteLine(Counter); 
      // Register the first step in the thread pool 
      ThreadPool.RegisterWaitForSingleObject(WaitHandle, AsyncIOMethod, null, 500, true); 
      // We force the main thread to wait here so that the demo doesn't close instantly 
      StopWaitHandle.WaitOne(); 
     } 
    } 
} 

También tendrá que registrar su aplicación IHttpAsyncHandler con IIS en cualquier forma apropiada para su situación.

+1

Manejo de muchas otras cosas, como cookies, redirigiendo a los usuarios a otras páginas (redirección HTTP). .. y no estoy seguro si con esta solución tengo que profundizar dentro de IIS y manejar todo yo mismo. Leería sobre eso y votaría. Gracias por la pista. – Xaqron

32

Echemos un vistazo a si la respuesta de Michael parece razonable.

Ahora, Michael sabiamente señala que Thread.Sleep(500) no debería costar mucho en el camino de la CPU. Eso está muy bien en teoría, pero veamos si eso sale bien en la práctica.

static void Main(string[] args) { 
     for(int i = 0; i != 10000; ++i) 
     { 
      Thread.Sleep(500); 
     } 
    } 

Al ejecutar esto, el uso de la CPU de la aplicación oscila alrededor del 0%.

Michael también señala que dado que todos los hilos que ASP.NET tiene que utilizar están durmiendo, tendrá que generar nuevos hilos, y ofrece que esto es costoso. Vamos a tratar de no dormir, pero haciendo un montón de desove:

static void Main(string[] args) { 
     for(int i = 0; i != 10000; ++i) 
     { 
      new Thread(o => {}).Start(); 
     } 
    } 

Creamos un montón de hilos, pero que acaba de ejecutar una operación nula. Eso usa mucha CPU, aunque los hilos no están haciendo nada.

Sin embargo, la cantidad total de hilos nunca es muy alta, ya que cada uno vive durante un tiempo tan corto. Deja para combinar los dos:

static void Main(string[] args) { 
     for(int i = 0; i != 10000; ++i) 
     { 
      new Thread(o => {Thread.Sleep(500);}).Start(); 
     } 
    } 

La adición de esta operación que hemos demostrado a ser baja en el uso de la CPU a cada hilo aumenta el uso de CPU aún más, a medida que aumentan los hilos arriba. Si lo ejecuto en un depurador, sube hasta cerca del 100% de la CPU. Si lo ejecuto fuera de un depurador, funciona un poco mejor, pero solo porque arroja una excepción de falta de memoria antes de que tenga la oportunidad de alcanzar el 100%.

Por lo tanto, no es Thread.Sleep en sí mismo ese es el problema, pero el efecto secundario de tener todos los subprocesos disponibles obliga a crear más y más hilos para manejar otro trabajo, tal como dijo Michael.

+0

Gracias Jon. Entonces, cuando los hilos funcionan, son menos que (en número) cuando están funcionando (esto tiene sentido). Como dije, repetí la prueba con la misma condición sin Thread.Sleep() y todo estaba bien con 100 clientes. Quise decir que los 100 clientes estaban conectados y recibiendo datos. Pero los mismos hilos comienzan a aumentar el uso de CPU cuando agregué Thread.Sleep() y nada más. ¿Qué piensas de IHttpAsyncHandler (gracias Lunatic) y rediseñar el trabajo? (Me preguntaba si ASP.NET es bueno para ese tipo de trabajo) – Xaqron

+0

Debido a la forma en que un grupo trata las tareas, 1 hilo físico puede servir a más de 1 cliente. Sin embargo, bloquear un hilo durmiéndolo, y ya no puede hacerlo, por lo que el número de subprocesos aumentará aunque el número de clientes no lo haga. Un controlador asíncrono es un buen enfoque para las tareas de asp retrasado en general, pero no estoy seguro de lo que está haciendo para dormir. Si desea acelerar los datos, tal vez use la aceleración de velocidad de bits IIS7; se puede usar con archivos no multimedia sin tasas de bit según http://learn.iis.net/page.aspx/149/bit-rate-throttling-extensibility-walkthrough/ –

+0

¡Hermosa respuesta, felicitaciones! –

0

Es porque el hilo recibe un aumento de prioridad cada vez que cede su porción de tiempo. Evite llamar a dormir a menudo (especialmente con valores bajos).

Cuestiones relacionadas