2009-09-01 15 views
20

Bien, simplemente no puedo entender bien los escenarios de subprocesos múltiples correctamente. Perdón por hacer una pregunta similar nuevamente, solo estoy viendo muchos "hechos" diferentes en Internet.¿Bloquea correctamente una lista <T> en escenarios MultiThreaded?

public static class MyClass { 
    private static List<string> _myList = new List<string>; 
    private static bool _record; 

    public static void StartRecording() 
    { 
     _myList.Clear(); 
     _record = true; 
    } 

    public static IEnumerable<string> StopRecording() 
    { 
     _record = false; 
     // Return a Read-Only copy of the list data 
     var result = new List<string>(_myList).AsReadOnly(); 
     _myList.Clear(); 
     return result; 
    } 

    public static void DoSomething() 
    { 
     if(_record) _myList.Add("Test"); 
     // More, but unrelated actions 
    } 
} 

La idea es que si se activa la grabación, llama a HacerAlgo() quedarán registradas en una lista interna, y regresó cuando StopRecording() es llamado.

Mi especificación es la siguiente:

  • StartRecording no se considera seguro para subprocesos. El usuario debe llamar a esto mientras que ningún otro Thread llama a DoSomething(). Pero si de alguna manera podría ser, sería genial.
  • StopRecording tampoco es oficialmente seguro para subprocesos. Una vez más, sería genial si pudiera ser, pero eso no es un requisito.
  • HacerAlgo tiene que ser seguro para subprocesos

La forma más habitual parece ser:

public static void DoSomething() 
    { 
     object _lock = new object(); 
     lock(_lock){ 
      if(_record) _myList.Add("Test"); 
     } 
     // More, but unrelated actions 
    } 

Alternativamente, se declara una variable estática:

private static object _lock; 

    public static void DoSomething() 
    { 
     lock(_lock){ 
      if(_record) _myList.Add("Test"); 
     } 
     // More, but unrelated actions 
    } 

Sin embargo, this answer dice que esta no impide que otros códigos accedan a él.

así que me pregunto

  • ¿Cómo me cerraba bien una lista?
  • ¿Debo crear el objeto de bloqueo en mi función o como una variable de clase estática?
  • ¿Puedo ajustar la funcionalidad de inicio y detención de grabación también en un bloqueo de bloqueo?
  • StopRecording() hace dos cosas: Establecer una variable booleana en falso (para evitar que DoSomething() agregue más cosas) y luego copiar la lista para devolver una copia de los datos al llamador). Supongo que _record = falso; es atómico y estará en efecto inmediatamente? Entonces, normalmente, no tendría que preocuparme por Multi-Threading aquí, a menos que algún otro Thread llame a StartRecording() nuevamente.

Al final del día, estoy buscando una forma de expresar "Está bien, esta lista es mía ahora, todos los otros hilos tienen que esperar hasta que haya terminado con eso".

+0

si entiendo lo que necesita echar un vistazo, tuve el mismo problema https://stackoverflow.com/a/44414120/4745606 –

Respuesta

27

Fijaré el _myList aquí porque es privado, pero usar una variable separada es más común. Para mejorar en algunos puntos:

public static class MyClass 
{ 
    private static List<string> _myList = new List<string>; 
    private static bool _record; 

    public static void StartRecording() 
    { 
     lock(_myList) // lock on the list 
     { 
      _myList.Clear(); 
      _record = true; 
     } 
    } 

    public static IEnumerable<string> StopRecording() 
    { 
     lock(_myList) 
     { 
      _record = false; 
      // Return a Read-Only copy of the list data 
      var result = new List<string>(_myList).AsReadOnly(); 
      _myList.Clear(); 
      return result; 
     } 
    } 

    public static void DoSomething() 
    { 
     lock(_myList) 
     { 
      if(_record) _myList.Add("Test"); 
     } 
     // More, but unrelated actions 
    } 
} 

Tenga en cuenta que este código utiliza lock(_myList) para sincronizar el acceso a ambos _myList y _record. Y necesita sincronizar todas las acciones en esos dos.

Y para estar de acuerdo con las otras respuestas aquí, lock(_myList) no le hace nada a _myList, simplemente usa _myList como un token (presumiblemente como clave en un HashSet). Todos los métodos deben jugar limpio pidiendo permiso usando la misma ficha. Un método en otro subproceso aún puede usar _myList sin bloqueo primero, pero con resultados impredecibles.

Podemos utilizar cualquier símbolo por lo que a menudo crean una especialmente:

private static object _listLock = new object(); 

y luego usar lock(_listLock) en lugar de lock(_myList) todas partes.

Esta técnica hubiera sido recomendable si myList hubiera sido pública, y hubiera sido absolutamente necesario si hubiera vuelto a crear myList en lugar de llamar a Clear().

+1

+1 para mencionar el peligro de bloqueo con la lista directamente. –

+2

Todas las respuestas aquí fueron muy útiles (así que, por favor, querido usuario que vino de una búsqueda en google, léalas todas), pero estoy aceptando esto debido a la parte "Token" que inmediatamente me dejó claro el concepto. Todavía creé mi propio objeto estático de solo lectura RecordingLock solo porque quería tener un buen nombre para él. –

12

Creación de una nueva cerradura en DoSomething() sería sin duda estar equivocado - no tendría sentido, ya que cada llamada a DoSomething() usaría una cerradura diferente. Se debe utilizar la segunda forma, pero con un inicializador:

private static object _lock = new object(); 

Es cierto que el bloqueo no se detiene todo lo demás accedan a su lista, pero a menos que estés exponiendo la lista directamente, eso no importa: nada más accederá a la lista de todos modos.

Sí, puede ajustar Start/StopRecording en bloqueos de la misma manera.

Sí, establecer una variable booleana es atómica, pero eso no lo hace seguro para subprocesos.Si solo accede a la variable dentro del mismo bloqueo, está bien en términos de la volatilidad y. De lo contrario, es posible que vea valores "obsoletos", p. establece el valor en true en un subproceso y otro subproceso podría usar un valor en caché al leerlo.

+0

Así que si HacerAlgo() utiliza una cerradura y tres subprocesos tienen acceso a la función, a continuación, los otros dos hilos esperarán hasta que finalice el número uno ¿Qué sucede si Start/Stop usa el mismo candado? ¿Se pondrían en cola todas las solicitudes o podrían pasar cosas malas? –

+0

Todas las solicitudes se pondrían en cola. Básicamente, la sentencia 'lock' es azúcar para' Monitor.Enter' que bloquea hasta que se pueda adquirir el bloqueo. –

+2

Y sí, todos los métodos deben usar __ el mismo objeto__ para bloquear. Es un sustituto de lock (_myList) –

1

Puede estar malinterpretando el this answer, lo que en realidad se afirma es que la declaración lock no bloquea realmente el objeto en cuestión, sino que impide que se ejecute cualquier otro código que utilice ese objeto como fuente de bloqueo.

Lo que realmente significa es que cuando utiliza la misma instancia que el objeto de bloqueo, el código dentro del bloque de bloqueo no debe ejecutarse.

Básicamente, no está intentando "bloquear" su lista, está intentando tener una instancia común que pueda usarse como punto de referencia para cuando quiera modificar su lista, cuando esté en uso o " bloqueado "desea evitar que se ejecute otro código que potencialmente podría modificar la lista.

+0

Bien, por lo que respecta al diseño y en términos generales: si tengo una o más variables que necesitan una modificación segura de subprocesos, debería tener un objeto de bloqueo común porque cada objeto de bloqueo puede solo estar bloqueado por un hilo. Pero podría tener lockObject1 y lockObject2 que luego podrían modificar mi Lista y causar todos los problemas habituales de multi-threading. –

+1

Sí, porque los hilos separados podrían bloquear las diferentes instancias del objeto y dado que cada uno tiene un bloqueo en el objeto particular en cuestión, se ejecutará el código dentro del bloque de bloqueo. Al igual que Jon había mencionado, se trata esencialmente de un monitor. Invoque la llamada que bloquea hasta que la instancia de objeto de bloqueo en cuestión haya ejecutado el código asociado con este en contexto. –

3

La primera forma es incorrecta, ya que cada persona que llama se bloqueará en un objeto diferente. Puede simplemente bloquear en la lista.

lock(_myList) 
{ 
    _myList.Add(...) 
} 
6

Existen varias formas de bloquear la lista. Puede bloquear en _myList directamente siempre que _myList nunca se cambie para hacer referencia a una nueva lista.

lock (_myList) 
{ 
    // do something with the list... 
} 

Se puede crear un bloqueo de objetos específicamente para este propósito.

private static object _syncLock = new object(); 
lock (_syncLock) 
{ 
    // do something with the list... 
} 

Si la colección estática implementa la interfaz System.Collections.ICollection (Lista (T) lo hace), también puede sincronizar mediante la propiedad SyncRoot.

lock (((ICollection)_myList).SyncRoot) 
{ 
    // do something with the list... 
} 

El principal punto a entender es que desea uno y único objeto de usar como su centinela de bloqueo, por lo que la creación de la centinela de bloqueo dentro del HacerAlgo() Won función no funciona Como dijo Jon, cada hilo que llame a DoSomething() obtendrá su propio objeto, por lo que el bloqueo de ese objeto tendrá éxito cada vez y otorgará acceso inmediato a la lista. Al hacer que el objeto de bloqueo sea estático (a través de la propia lista, un objeto de bloqueo dedicado o la propiedad ICollection.SyncRoot), se comparte entre todos los hilos y puede serializar efectivamente el acceso a su lista.

Cuestiones relacionadas