2012-03-15 17 views
52

¿Es eficiente aSpinWait vs espera en espera. ¿Cuál usar?

SpinWait.SpinUntil(() => myPredicate(), 10000) 

para un tiempo de espera de 10000ms

o

¿Es más eficaz utilizar Thread.Sleep de votación para la misma condición Por ejemplo algo en la línea de la siguiente SleepWait función:

public bool SleepWait(int timeOut) 
{ 
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start(); 
    while (!myPredicate() && stopwatch.ElapsedMilliseconds < timeOut) 
    { 
     Thread.Sleep(50) 
    } 
    return myPredicate() 
} 

Soy la estafa ¿Está seguro de que todo el rendimiento de SpinWait puede no ser un buen patrón de uso si estamos hablando de tiempos de espera superiores a 1 segundo? ¿Es esto una suposición válida?

¿Qué enfoque prefiere y por qué? ¿Hay otro enfoque aún mejor?


actualización - Ser más específico:

¿Hay una manera de hacer BlockingCollection pulso un hilo de dormir cuando llega a la capacidad limitada? Prefiero evitar las agitadas esperas, como sugiere Marc Gravel.

Respuesta

39

El enfoque mejor es tener algún mecanismo para detectar activamente lo convertirse en verdadero (en lugar de pasiva de votación para que tenga se convierten en verdad); esto podría ser cualquier tipo de dispositivo de espera, o tal vez un Task con Wait, o tal vez un event al que puede suscribirse para despegarse. Por supuesto, si haces ese tipo de "esperar hasta que pase algo", eso es todavía no es tan eficiente como simplemente hacer el siguiente trabajo como una devolución de llamada, lo que significa que no necesitas usar un hilo esperar. Task tiene ContinueWith para esto, o puede simplemente hacer el trabajo en un event cuando es despedido. El event es probablemente el enfoque más simple, según el contexto. Task, sin embargo, ya ofrece casi todo lo que usted está hablando aquí, incluidos los mecanismos de "espera con tiempo de espera" y "devolución de llamada".

Y sí, girar durante 10 segundos no es genial. Si desea usar algo como su código actual, y si tiene motivos para esperar un pequeño retraso, pero necesita permitir uno más largo, tal vez SpinWait para (digamos) 20 ms, y use Sleep para el resto?


Re el comentario; así es como me engancho un "que éste se llena" mecanismo:

private readonly object syncLock = new object(); 
public bool WaitUntilFull(int timeout) { 
    if(CollectionIsFull) return true; // I'm assuming we can call this safely 
    lock(syncLock) { 
     if(CollectionIsFull) return true; 
     return Monitor.Wait(syncLock, timeout); 
    } 
} 

con, en el código de "volver a poner en la colección":

if(CollectionIsFull) { 
    lock(syncLock) { 
     if(CollectionIsFull) { // double-check with the lock 
      Monitor.PulseAll(syncLock); 
     } 
    } 
} 
+0

Gracias, me gusta su respuesta, esto sería bonito. Supongamos que está rastreando el uso de algunos recursos a través de BlockingCollection. Se usan (y se eliminan de la colección) y se devuelven a la colección cuando vuelven a estar disponibles para su reutilización. Un cierre solo puede ocurrir cuando todos estos han regresado a la colección. ¿Cómo harías para indicar que un cierre puede continuar (es decir, la recolección está llena otra vez) que no sea esperar ocupado? – Anastasiosyal

+0

@Anastasiosyal Encapsularía la colección y haría que el envoltorio hiciera algo cuando estuviera completo; de hecho, probablemente usaría un 'Monitor' para esto (agregaré un ejemplo) –

+0

En el destructor de una clase 'ObjectPool' que desciende de BlockingQueue, extraiga todos los objetos del BlockingCollection uno por uno y deséchelos, (como esto es C#, establezca la referencia recuperada en nil), hasta que la cantidad de objetos reventados sea igual a la profundidad de la agrupación. Todos los objetos agrupados se han eliminado, por lo que puede deshacerse de la cola y regresar del dtor para continuar con la secuencia de apagado. ¡No se requiere votación/ocupado! Es posible que desee poner un poco de tiempo de espera en la toma para que un objeto filtrado genere una excepción (o algo). –

97

En .NET 4 SpinWait realiza intensivo de la CPU girando durante 10 iteraciones antes de ceder. Pero no vuelve a la persona que llama inmediatamente después de cada uno de esos ciclos; en su lugar, llama al Thread.SpinWait para girar a través del CLR (esencialmente el sistema operativo) durante un período de tiempo establecido.Este período de tiempo es inicialmente unas pocas decenas de nano segundos, pero se duplica con cada iteración hasta que se completen las 10 iteraciones. Esto permite claridad/previsibilidad en el tiempo total dedicado a la fase de giro (intensivo en CPU), que el sistema puede sintonizar de acuerdo con las condiciones (número de núcleos, etc.). Si SpinWait permanece en la fase de rendimiento de centrifugado por mucho tiempo, se suspenderá periódicamente para permitir que continúen los otros hilos (ver J. Albahari's blog para obtener más información). Este proceso está garantizada para mantener un núcleo ocupado ...

Así, SpinWait limita la hilatura intensivo de la CPU a un número fijo de iteraciones, después de lo cual da su porción de tiempo en cada vuelta (por activar realmente Thread.Yield y Thread.Sleep) , disminuyendo su consumo de recursos. También detectará si el usuario está ejecutando una única máquina central y cederá en cada ciclo si ese es el caso.

Con Thread.Sleep es bloqueado el hilo. Pero este proceso no será tan caro como el anterior en términos de CPU.

+6

+1 Gracias por la claridad entre SpinWait y thread.Sleep – Anastasiosyal

+0

Esto se copia del sitio web de Albahari. ¡Debe ser referenciado! – georgiosd

+1

No se copia literalmente, pero fue influenciado por su sitio, por lo tanto, ¿por qué he hecho referencia a su blog? – MoonKnight