2010-03-19 14 views
32

Mi preocupación principal es con el indicador booleano ... ¿es seguro usarlo sin ninguna sincronización? He leído en varios lugares que es atómico (incluida la documentación).¿Es seguro usar un indicador booleano para detener la ejecución de un hilo en C#

class MyTask 
{ 
    private ManualResetEvent startSignal; 
    private CountDownLatch latch; 
    private bool running; 

    MyTask(CountDownLatch latch) 
    { 
     running = false; 
     this.latch = latch; 
     startSignal = new ManualResetEvent(false); 
    } 

    // A method which runs in a thread 
    public void Run() 
    { 
     startSignal.WaitOne(); 
     while(running) 
     { 
      startSignal.WaitOne(); 
      //... some code 
     } 
     latch.Signal(); 
    } 

    public void Stop() 
    { 
     running = false; 
     startSignal.Set(); 
    } 

    public void Start() 
    { 
     running = true; 
     startSignal.Set(); 
    } 

    public void Pause() 
    { 
     startSignal.Reset(); 
    } 

    public void Resume() 
    { 
     startSignal.Set(); 
    } 
} 

¿Es esta una forma segura de diseñar una tarea de esta manera? ¿Alguna sugerencia, mejoras, comentarios?

Nota: Escribí mi clase personalizada CountDownLatch en caso de que se esté preguntando de dónde estoy obteniendo.

Actualización:
Aquí es mi CountDownLatch también:

public class CountDownLatch 
{ 
    private volatile int m_remain; 
    private EventWaitHandle m_event; 

    public CountDownLatch (int count) 
    { 
     if (count < 0) 
      throw new ArgumentOutOfRangeException(); 
     m_remain = count; 
     m_event = new ManualResetEvent(false); 
     if (m_remain == 0) 
     { 
      m_event.Set(); 
     } 
    } 

    public void Signal() 
    { 
     // The last thread to signal also sets the event. 
     if (Interlocked.Decrement(ref m_remain) == 0) 
      m_event.Set(); 
    } 

    public void Wait() 
    { 
     m_event.WaitOne(); 
    } 
} 

Respuesta

45

Será mejor que lo marca volatile sin embargo:

La palabra clave volátiles indica que un campo se puede modificar b y múltiple al mismo tiempo que ejecuta subprocesos. Los campos que se declaran volátiles no son sujetos a optimizaciones del compilador que asumen el acceso por un único hilo. Este garantiza que el valor más actualizado esté presente en el campo en todo momento.

Pero que cambiaría su bucle:

startSignal.WaitOne(); 
    while(running) 
    { 
     //... some code 
     startSignal.WaitOne(); 
    } 

Como está en su puesto el 'algo de código' podría ejecutar cuando el hilo es detuvo (es decir, cuando la parada se llama.), Que es inesperado y puede ser incluso incorrecto.

+0

@Remus Buena captura en mi orden de código while loop. En cuanto a la bandera volátil: ¿realmente hace una diferencia si la bandera es volátil en este caso? Si pierde la primera actualización, hará una ejecución más por el ciclo y la capturará la próxima vez ... – Kiril

+6

Si no lo marca como volátil, el código generado podría optimizar el valor en un registro y su thread * never * verá el cambio. –

+4

@Remus Lo entiendo ahora: la atomicidad no tiene nada que ver con la visibilidad entre los hilos ... El hecho de que una operación se ejecute en un ciclo de CPU no significa que el resultado será visible para los otros hilos a menos que el valor está marcado como volátil. – Kiril

4

Los booleanos son atómicos en C#, sin embargo, si quiere modificarlo en un hilo y leerlo en otro, deberá marcarlo como mínimo volátil. De lo contrario, el hilo de lectura solo puede leerlo una vez en un registro.

+0

@Michael Entonces, ¿cuántos ciclos a través del bucle pueden pasar de manera realista antes de que el bucle lo atrape? Supuse que sería uno, pero supongo que no hay tal garantía ... – Kiril

+0

@Michael Lo tengo ahora: me di cuenta de que estaba confundiendo la atomicidad con la visibilidad. – Kiril

0

Por cierto, me he dado cuenta de esta parte del código:

// A method which runs in a thread 
    public void Run() 
    { 
     startSignal.WaitOne(); 
     while(running) 
     { 
      startSignal.WaitOne(); 
      //... some code 
     } 
     latch.Signal(); 
    } 

Usted tendrá que desbloquear el subproceso de trabajo dos veces usando "startSignal.Set()" para el código dentro del bloque, mientras que se ejecute.

¿Esto es deliberado?

+2

@Akapetronics startSignal solo necesita establecerse una vez. WaitOne no restablece el evento, por lo que puedo llamar a startSignal.WaitOne() varias veces y no se bloqueará hasta que se llame a startSignal.Reset(). El diseño es deliberado: el hilo se bloqueará hasta que se llame al método Start y el método Start establezca primero el indicador de ejecución y establezca la señal startSignal para que el ciclo while pueda comenzar la ejecución. Si invierto el orden y primero configuro startSignal, entonces el ciclo while podría salir antes de que yo establezca el indicador de ejecución. Tenga en cuenta que este diseño elimina la necesidad de cerraduras. – Kiril

Cuestiones relacionadas