2011-07-04 9 views
6

Soy un poco nuevo en la programación concurrente, y trato de comprender los beneficios de usar Monitor.Pulse y Monitor.Wait.¿Qué son las ventajas de Monitor.Pulse And Monitor.Wait?

el ejemplo de MSDN es la siguiente:

class MonitorSample 
{ 
    const int MAX_LOOP_TIME = 1000; 
    Queue m_smplQueue; 

    public MonitorSample() 
    { 
     m_smplQueue = new Queue(); 
    } 
    public void FirstThread() 
    { 
     int counter = 0; 
     lock(m_smplQueue) 
     { 
      while(counter < MAX_LOOP_TIME) 
      { 
       //Wait, if the queue is busy. 
       Monitor.Wait(m_smplQueue); 
       //Push one element. 
       m_smplQueue.Enqueue(counter); 
       //Release the waiting thread. 
       Monitor.Pulse(m_smplQueue); 

       counter++; 
      } 
     } 
    } 
    public void SecondThread() 
    { 
     lock(m_smplQueue) 
     { 
      //Release the waiting thread. 
      Monitor.Pulse(m_smplQueue); 
      //Wait in the loop, while the queue is busy. 
      //Exit on the time-out when the first thread stops. 
      while(Monitor.Wait(m_smplQueue,1000)) 
      { 
       //Pop the first element. 
       int counter = (int)m_smplQueue.Dequeue(); 
       //Print the first element. 
       Console.WriteLine(counter.ToString()); 
       //Release the waiting thread. 
       Monitor.Pulse(m_smplQueue); 
      } 
     } 
    } 
    //Return the number of queue elements. 
    public int GetQueueCount() 
    { 
     return m_smplQueue.Count; 
    } 

    static void Main(string[] args) 
    { 
     //Create the MonitorSample object. 
     MonitorSample test = new MonitorSample();   
     //Create the first thread. 
     Thread tFirst = new Thread(new ThreadStart(test.FirstThread)); 
     //Create the second thread. 
     Thread tSecond = new Thread(new ThreadStart(test.SecondThread)); 
     //Start threads. 
     tFirst.Start(); 
     tSecond.Start(); 
     //wait to the end of the two threads 
     tFirst.Join(); 
     tSecond.Join();   
     //Print the number of queue elements. 
     Console.WriteLine("Queue Count = " + test.GetQueueCount().ToString()); 
    } 
} 

y no puedo ver el beneficio de usar Esperar y el pulso en lugar de esto:

public void FirstThreadTwo() 
    { 
     int counter = 0; 
     while (counter < MAX_LOOP_TIME) 
     { 
      lock (m_smplQueue) 
      { 
       m_smplQueue.Enqueue(counter); 
       counter++; 
      } 
     } 
    } 
    public void SecondThreadTwo() 
    { 
     while (true) 
     { 
      lock (m_smplQueue) 
      { 
        int counter = (int)m_smplQueue.Dequeue(); 
        Console.WriteLine(counter.ToString()); 
      } 
     } 
    } 

Cualquier ayuda es muy apreciada. Gracias

Respuesta

12

Para describir "ventajas", una pregunta clave es "¿sobre qué?". Si quiere decir "con preferencia a un hot-loop", bueno, la utilización de la CPU es obvia. Si usted quiere decir "con preferencia a un sueño/reintentar bucle" - se puede obtener una respuesta mucho más rápida (Pulse no tiene que esperar el tiempo) y el uso de CPU inferior (no se ha despertado 2000 veces innecesariamente).

Aunque, en general, las personas significan "con preferencia a Mutex, etc.".

Tiendo a utilizar estos ampliamente, incluso con preferencia a mutex, reset-events, etc; razones:

  • son simples, y cubren la mayor parte de los escenarios que necesito
  • son relativamente baratos, ya que no tienen que ir todo el camino a identificadores de sistema operativo (a diferencia de objeto mutex etc, que es propiedad por el sistema operativo)
  • general estoy ya usando lock para manejar la sincronización, por lo que es muy probable que ya tengo un lock cuando tengo que esperar a que algo
  • alcanza mi objetivo normales - que permite a 2 hilos finalización de la señal entre sí de forma gestionada
  • rara vez necesito las otras características del objeto mutex etc (tales como ser entre procesos)
+0

hey, gracias por la respuesta rápida. con respecto a la pregunta sobre - sobre el uso de Monitor.Enter y Monitor.Exit, No veo realmente cómo Pulse y Wait difieren tanto que con estos dos métodos, solo que quizás en términos de costo de rendimiento. – seren1ty

+0

@ seren1ty lo hacen *** completamente *** cosas diferentes; Entrar/Salir adquirir y liberar un bloqueo; Wait abre el bloqueo, entra en la cola de espera (esperando un pulso) y luego (cuando se despierta) vuelve a adquirir el bloqueo; El pulso mueve los elementos en espera de la cola de espera a la lista de espera. *** completamente *** diferente (aunque de cortesía) uso. Pulse/Wait se utilizan para * coordinar * entre subprocesos, en lugar de necesitar un bucle caliente o frío. –

3

Es una mejora del rendimiento de usar Monitor.Pulse/Espera, como has adivinado. Es una operación relativamente costosa adquirir un candado. Al usar Monitor.Wait, su hilo quedará inactivo hasta que otro subproceso despierte su subproceso con `Monitor.Pulse '.

Verá la diferencia en TaskManager porque un núcleo de procesador se fijó incluso cuando no hay nada en la cola.

4

hay un defecto grave en el fragmento, SecondThreadTwo() fallará mal cuando se trata de llamar a quitar de la cola() en una cola vacía. Probablemente haya funcionado haciendo que FirstThreadTwo() ejecute una fracción de segundo antes del hilo del consumidor, probablemente al iniciarlo primero. Eso es un accidente, uno que dejará de funcionar después de ejecutar estos hilos por un tiempo o comenzarlos con una carga de máquina diferente. Esto puede funcionar accidentalmente sin errores durante bastante tiempo, muy difícil de diagnosticar la falla ocasional.

No hay manera de escribir un algoritmo de bloqueo que bloquea el consumidor hasta que la cola se convierte en no vacío con sólo la declaración de bloqueo. Un bucle ocupado que entra y sale constantemente del bloqueo funciona, pero es un sustituto muy pobre.

Escribir este tipo de código es mejor dejar a los gurús de roscado, es muy difícil de probar que funciona en todos los casos. No solo la ausencia de modos de falla como esta o las carreras de subprocesos. Pero también la adecuación general del algoritmo que evita el interbloqueo, el livelock y el hilo de los convoys. En el mundo de .NET, los gurús son Jeffrey Richter y Joe Duffy. Ellos comen diseños de bloqueo para el desayuno, tanto en sus libros y sus blogs y artículos de revistas. Robar su código es esperado y aceptado. Y entró parcialmente en el marco .NET con las adiciones en el espacio de nombres System.Collections.Concurrent.

0

Las ventajas de Pulse y Wait son que pueden ser utilizados como bloques de construcción para todos los demás mecanismos de sincronización incluyendo exclusiones mutuas, eventos, barreras, etc. Hay cosas que se pueden hacer con Pulse y Wait que no se pueden hacer con ningún otro mecanismo de sincronización en el BCL.

Todas las cosas interesantes ocurren dentro del método Wait. Wait saldrá de la sección crítica y pondrá el hilo en el estado WaitSleepJoin colocándolo en la cola de espera. Una vez que se llama al Pulse, el siguiente hilo en la cola de espera se mueve a la cola lista. Una vez que el hilo pasa al estado Running, vuelve a entrar en la sección crítica. Esto es importante para repetir de otra manera. Wait liberará el bloqueo y lo volverá a adquirir de forma atómica. Ningún otro mecanismo de sincronización tiene esta característica.

La mejor manera de visualizar esto es tratar de replicar el comportamiento con alguna otra estrategia y luego ver qué puede salir mal. Permítanos probar esta excerise con un ManualResetEvent ya que los métodos Set y WaitOneparecen como pueden ser análogos. Nuestro primer intento podría verse así.

void FirstThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    mre.Set(); 
    // Do stuff. 
    } 
} 

void SecondThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    while (!CheckSomeCondition()) 
    { 
     mre.WaitOne(); 
    } 
    // Do stuff. 
    } 
} 

Debería ser fácil ver que el código puede atascarse. Entonces, ¿qué sucede si probamos esta solución ingenua?

void FirstThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    mre.Set(); 
    // Do stuff. 
    } 
} 

void SecondThread() 
{ 
    lock (mre) 
    { 
    // Do stuff. 
    } 
    while (!CheckSomeCondition()) 
    { 
    mre.WaitOne(); 
    } 
    lock (mre) 
    { 
    // Do stuff. 
    } 
} 

¿Puedes ver lo que puede salir mal aquí? Como no volvimos a ingresar atómicamente al bloqueo después de que se verificó la condición de espera, podría entrar otro hilo e invalidar la condición. En otras palabras, otro subproceso podría hacer algo que ocasione que CheckSomeCondition comience a devolver false antes de que se vuelva a adquirir el siguiente bloqueo. Eso definitivamente puede causar muchos problemas extraños si su segundo bloque de código requiere que la condición sea true.

Cuestiones relacionadas