2011-07-29 6 views
9

Parece haber mucho que aprender acerca de la programación multiproceso y es un poco intimidante.¿La forma más simple de hacer que todo un método sea seguro para subprocesos?

Para mis necesidades actuales, sólo quiero para proteger contra un método que se llama de nuevo desde otro hilo antes de que termine, y mi pregunta es:

¿Es esta una forma adecuada (seguro) para hacer una método de seguridad de subprocesos?

class Foo 
{ 
    bool doingWork; 
    void DoWork() 
    { 
     if (doingWork) // <- sophistocated thread-safety 
      return;  // <- 

     doingWork = true; 

     try 
     { 
      [do work here] 
     } 
     finally 
     { 
      doingWork = false; 
     } 
    } 
} 

Si eso no es suficiente, ¿cuál es la forma más sencilla de lograr esto?


EDIT: Más información sobre el escenario:

  • No es sólo una instancia de Foo

  • Foo.DoWork() se llamará a partir de un hilo en el ThreadPool Evento transcurrido de de un System.Timers.Timer.

  • Normalmente Foo.DoWork() terminará millones de años antes de que la próxima vez que es llamada , pero quiero codificar la pequeña posibilidad de que se ejecutará siempre, y llamará de nuevo antes de terminar.


(Yo tampoco soy lo suficientemente inteligente como para estar seguro de si esta cuestión podría ser etiquetado independiente del idioma, por lo que no tienen. Lectores ilustrados, se sienten en libertad de hacerlo si procede.)

+0

¿Es un objeto de tipo Foo instanciado para cada subproceso o se comparte en varios subprocesos? – NotMe

+0

Proporcione más detalles sobre el código que método call dowork, ¿hay múltiples hilos? – sll

+0

Respalda la compatibilidad o diseña tu código para que no pueda suceder.Simplemente rescatar cuando sucede no es probable que sea la solución correcta. Pero su edición deja en claro que la seguridad del hilo es en realidad el problema más que la reentrada. –

Respuesta

8

Su código no es seguro para subprocesos. Debería usar la palabra clave lock en su lugar.

En su código actual:

if (doingWork) 
     return; 

    // A thread having entered the function was suspended here by the scheduler. 

    doingWork = true; 

Cuando el siguiente hilo llega a través, sino que también entrar en la función.

Es por esto que se debe usar la construcción lock. Básicamente hace lo mismo que su código, pero sin el riesgo de un hilo que se interrumpió en el medio:

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     lock(lockObject) 
     { 
      [do work here] 
     } 
    } 
} 

Tenga en cuenta que este código tiene un tanto diferentes semánticas que su original. Este código hará que el segundo hilo que ingresa espere y luego haga el trabajo. Tu código original hizo que el segundo hilo simplemente abortara. Para acercarse a su código original, no se puede usar la declaración C# lock. El Monitor constructo subyacente tiene que ser utilizado directamente:

class Foo 
{ 
    object lockObject = new object; 
    void DoWork() 
    { 
     if(Monitor.TryEnter(lockObject)) 
     { 
      try 
      { 
       [do work here] 
      } 
      finally 
      { 
       Monitor.Exit(lockObject); 
      } 
     } 
    } 
} 
+0

Realmente no necesito abortar, por lo que el método de bloqueo debería estar bien. Dulce, ¡eso es simple! Gracias. –

+0

[Valentin] (http://stackoverflow.com/questions/6879468/simplest-way-to-make-a-whole-method-thread-safe/6879698#6879698) tiene una forma de abortar con solo bloquear () Interesado en tus comentarios al respecto. –

0

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

posible que desee ver en el uso de barrera en su lugar, se hace todo el trabajo por usted. Es la forma estándar de controlar el código de reentrada. También le permite controlar la cantidad de hilos que hacen ese trabajo a la vez (si permite más de 1).

+0

Eso me parece demasiado complejo. Realmente solo quiero lograr lo que implica mi fragmento de código: saltar cuando vuelva a ingresar. –

+0

Verifique el recuento de participantes en la barrera, si es> 0, de lo contrario, agregue un participante y trabaje; al final, elimine al participante. La barrera está diseñada para ser segura para hilos. –

+0

¿Puedes hacer el control y agregar un participante de forma atómica? ¿Si es así, cómo? –

3

La reentrada no tiene nada que ver con multi-threading.

Un método de reentrada es un método que puede terminar siendo llamado desde su interior, en el mismo hilo.
Por ejemplo, si un método genera un evento, y el código del cliente que maneja ese evento vuelve a llamar al método dentro del controlador de eventos, ese método es reentrante.
Proteger ese método de la reentrada significa asegurarse de que si lo llamas desde dentro de sí mismo, no hará nada o lanzará una excepción.

Su código está protegido contra la reentrada dentro de la misma instancia de objeto, siempre y cuando todo esté en el mismo hilo.

A menos que [do work here] sea capaz de ejecutar código externo (por ejemplo, al plantear un evento, o al llamar a un delegado o un método de otra cosa), no es reentrante en primer lugar.

Su pregunta editada indica que toda esta sección es irrelevante para usted.
Probablemente deberías leerlo de todos modos.


Usted puede ser (EDITAR: son) en busca de exclusividad – asegurar que el método no funcionará dos veces a la vez si es llamado por múltiples hilos simultáneamente.
Su código no es exclusivo. Si dos subprocesos ejecutan el método a la vez, y ambos ejecutan la instrucción if a la vez, ambos superarán el if, luego ambos configurarán el indicador doingWork, y ambos ejecutarán el método completo.

Para hacer eso, use la palabra clave lock.

+0

Bien, dijo multithreading en las etiquetas entonces uno solo puede suponer. –

+1

Uno solo puede asumir _what_? La pregunta no tiene nada que ver con multi-threading. ** EDIT **: Ahora sí. – SLaks

+0

Es cierto que solo estaba pensando en el método que se llama de nuevo desde otro hilo. Gracias por señalar esto. He editado la pregunta para que quede claro al respecto. Sin embargo, creo que el término también se puede aplicar a un escenario de múltiples hilos. Si no, ¿cómo lo llamarías cuando un método necesita ser capaz de hacer frente a la llamada desde otro hilo mientras ya se está ejecutando? –

2

si quieres fácil código y no se preocupan por demasiado el rendimiento puede ser tan fácil como

class Foo 
{ 
    bool doingWork; 
object m_lock=new object(); 
    void DoWork() 
    { 
     lock(m_lock) // <- not sophistocated multithread protection 
{ 
     if (doingWork) 
      return;  
     doingWork = true; 
} 


     try 
     { 
      [do work here] 
     } 
     finally 
     { 
lock(m_lock) //<- not sophistocated multithread protection 
{ 
      doingWork = false; 
} 
     } 
    } 

}

Si Si desea encapsular un poco el bloqueo, puede crear una propiedad que sea segura para hilos de la siguiente manera:

public bool DoingWork 
{ 
get{ lock(m_Lock){ return doingWork;}} 
set{lock(m_lock){doingWork=value;}} 
} 

Ahora puede usarlo en su lugar campo, sin embargo, dará lugar a más tiempo para bloquear causa aumento de número de usos de bloqueo.

O puede utilizar el enfoque de cerco completo (de gran libro de roscado Joseph Albahari online threading)

class Foo 
{ 
    int _answer; 
    bool _complete; 

    void A() 
    { 
    _answer = 123; 
    Thread.MemoryBarrier(); // Barrier 1 
    _complete = true; 
    Thread.MemoryBarrier(); // Barrier 2 
    } 

    void B() 
    { 
    Thread.MemoryBarrier(); // Barrier 3 
    if (_complete) 
    { 
     Thread.MemoryBarrier();  // Barrier 4 
     Console.WriteLine (_answer); 
    } 
    } 
} 

Afirma que valla completo es 2 veces más rápido que la instrucción lock. En algunos casos, puede mejorar el rendimiento eliminando llamadas innecesarias a MemoryBarrier(), pero usar lock es simple, más claro y menos propenso a errores.

Creo que esto también se puede hacer utilizando la clase Interlocked alrededor del campo int workWork.

+0

¿Existe alguna diferencia notable entre su bloqueo() (bloqueo de acceso a doingWork) y el método de bloqueo() de Anders (bloqueo de acceso al código que hace el trabajo)? –

+0

hay una diferencia lógica. Como puedo ver por su método de respuesta es reentrante, y otras llamadas se ponen en cola. En mi código supongo que no desea permitir que otros subprocesos pongan en cola este método, si se está ejecutando en un subproceso. En mi experiencia, mi enfoque es a menudo preferido, por ejemplo, cuando no desea permitir al usuario iniciar varias solicitudes de servidor haciendo doble clic en un botón. PD: cuando dice que no se puede hacer usando el bloqueo, lo hago usando el bloqueo. Monitor.TryEnter puede ser un mejor rendimiento, pero un poco más avanzado para las necesidades diarias, supongo, id lo dejo para el código de rendimiento crítico. –

+0

Oh, ya veo, genial. ¿Cómo te gusta eso, Anders? :) –

Cuestiones relacionadas