tpl threading thread programming parallel net .net multithreading concurrency

.net - threading - thread programming c#



SwitchToThread/Thread.Yield vs. Thread.Sleep(0) vs. Thead.Sleep(1) (4)

Además de otras respuestas, aquí hay algunos números de creación de perfiles.

(!) ¡No tome este perfil demasiado en serio! Está hecho solo para ilustrar las respuestas anteriores en números y comparar aproximadamente la magnitud de los valores.

static void Profile(Action func) { var sw = new Stopwatch(); var beginTime = DateTime.Now; ulong count = 0; while (DateTime.Now.Subtract(beginTime).TotalSeconds < 5) { sw.Start(); func(); sw.Stop(); count++; } Console.WriteLine($"Made {count} iterations in ~5s. Total sleep time {sw.ElapsedMilliseconds}[ms]. Mean time = {sw.ElapsedMilliseconds/(double) count} [ms]"); } Profile(()=>Thread.Sleep(0)); Profile(()=>Thread.Sleep(1)); Profile(()=>Thread.Yield()); Profile(()=>Thread.SpinWait(1));

Los resultados de los bucles giratorios para ~ 5s:

Function | CPU % | Iters made | Total sleep | Invoke | | | time [ms] | time [ms] ===================================================================== Sleep(0) | 100 0 | 2318103 | 482 | 0.00020 Sleep(1) | 6 0 | 4586 | 5456 | 1.08971 Yield() | 100 0 | 2495220 | 364 | 0.00010 SpinWait(1)| 100 0 | 2668745 | 81 | 0.00003

Hecho con Mono 4.2.3 x86_64

Estoy tratando de escribir el último método de "Rendimiento" para ceder el segmento de tiempo actual a otros hilos. Hasta ahora, he descubierto que hay varias formas diferentes de hacer que el hilo ceda su porción de tiempo asignada. Solo quiero asegurarme de interpretarlos correctamente ya que la documentación no es muy clara. Entonces, por lo que he leído en stackoverflow, MSDN y varias publicaciones en el blog, existen las siguientes opciones que tienen diferentes ventajas / desventajas:

SwitchToThread [win32] / Thread.Yield [.NET 4 Beta 1]: cede a cualquier hilo en el mismo procesador

  • Ventaja: aproximadamente el doble de rápido que Thread.Sleep(0)
  • Desventaja: cede solo a los hilos en el mismo procesador

Thread.Sleep(0) : cede a cualquier hilo de igual o mayor prioridad en cualquier procesador

  • Ventaja: más rápido que Thread.Sleep(1)
  • Desventaja: cede solo a hilos de igual o mayor prioridad

Thread.Sleep(1) : cede a cualquier hilo en cualquier procesador

  • Ventaja: cede a cualquier hilo en cualquier procesador
  • Desventaja: la opción más lenta ( Thread.Sleep(1) generalmente suspenderá el hilo por aproximadamente 15 ms si no se usa timeBeginPeriod / timeEndPeriod [win32])

¿Qué pasa con Thread.SpinWait ? ¿Puede eso usarse para producir la porción de tiempo del hilo? Si no, ¿para qué se usa?

Hay algo más que me he perdido o interpretado incorrectamente. Estaría agradecido si pudieras corregir / agregar a mi entendimiento.

Así es como se ve mi método de rendimiento hasta ahora:

public static class Thread { [DllImport("kernel32.dll")] static extern bool SwitchToThread(); [DllImport("winmm.dll")] internal static extern uint timeBeginPeriod(uint period); [DllImport("winmm.dll")] internal static extern uint timeEndPeriod(uint period); /// <summary> yields time slice of current thread to specified target threads </summary> public static void YieldTo(ThreadYieldTarget threadYieldTarget) { switch (threadYieldTarget) { case ThreadYieldTarget.None: break; case ThreadYieldTarget.AnyThreadOnAnyProcessor: timeBeginPeriod(1); //reduce sleep to actually 1ms instead of system time slice with is around 15ms System.Threading.Thread.Sleep(1); timeEndPeriod(1); //undo break; case ThreadYieldTarget.SameOrHigherPriorityThreadOnAnyProcessor: System.Threading.Thread.Sleep(0); break; case ThreadYieldTarget.AnyThreadOnSameProcessor: SwitchToThread(); break; default: throw new ArgumentOutOfRangeException("threadYieldTarget"); } } } public enum ThreadYieldTarget { /// <summary> Operation system will decide when to interrupt the thread </summary> None, /// <summary> Yield time slice to any other thread on any processor </summary> AnyThreadOnAnyProcessor, /// <summary> Yield time slice to other thread of same or higher piority on any processor </summary> SameOrHigherPriorityThreadOnAnyProcessor, /// <summary> Yield time slice to any other thread on same processor </summary> AnyThreadOnSameProcessor }


El artículo "Cómo funciona Locks Lock" de Jeff Moser ( http://www.moserware.com/2008/09/how-do-locks-lock.html ) puede dar información interna sobre la mecánica de SpinWait. Para citar el documento:

¿Qué está haciendo exactamente? Observando clr / src / vm / comsynchronizable.cpp de Rotor nos da la realidad:

FCIMPL1 (void, ThreadNative :: SpinWait, int iterations) {WRAPPER_CONTRACT; STATIC_CONTRACT_SO_TOLERANT;

for(int i = 0; i < iterations; i++) YieldProcessor();

} FCIMPLEND

El buceo adicional muestra que "YieldProcessor" es esta macro:

#define YieldProcessor () __asm ​​{rep nop}

Esta es una instrucción de ensamblaje "repeat no-op". También se conoce en el manual de instrucciones de Intel como "PAUSE - Spin Loop Hint". Esto significa que la CPU sabe sobre la espera de giro que queremos lograr.

Relacionado: http://msdn.microsoft.com/en-us/library/ms687419(VS.85).aspx http://www.moserware.com/2008/09/how-do-locks-lock.html#lockfn7


SpinWait es útil en procesadores hyperthreaded. Con hyperthreading, múltiples subprocesos programados de sistema operativo se pueden ejecutar en el mismo procesador físico, compartiendo los recursos del procesador. SpinWait indica al procesador que no está haciendo ningún trabajo útil y que debe ejecutar código desde una CPU lógica diferente. Como su nombre indica, normalmente se usa cuando estás girando.

Supongamos que tiene un código como:

while (!foo) {} // Spin until foo is set.

Si este subproceso se ejecuta en un subproceso en un procesador hyperthreaded, consume recursos de procesador que podrían utilizarse para otros subprocesos que se ejecutan en el procesador.

Al cambiar a:

while (!foo) {Thread.SpinWait(1);}

Estamos indicando a la CPU que proporcione algunos recursos al otro hilo.

SpinWait no afecta la programación del sistema de subprocesos.

Para sus preguntas principales sobre el "Rendimiento máximo", depende en gran medida de su situación: no podrá obtener una buena respuesta sin aclarar por qué quiere un hilo ceder. Desde mi punto de vista, la mejor manera de ceder el procesador es hacer que el hilo entre en estado de espera y solo se despierte cuando haya trabajo por hacer. Cualquier otra cosa es simplemente perder tiempo de CPU.


SpinWait está diseñado para esperar sin ceder el tiempo actual

Está diseñado para situaciones en las que sabe que querrá hacer algo en muy poco tiempo, por lo que perder excesos de tiempo será excesivo.

Estaba bajo la impresión de Thread.Yield (x) para cualquier valor de x <el quantum del hilo era equivalente, incluso cero aunque no tengo puntos de referencia para ese efecto.