2010-02-13 20 views
5

Si tengo algo como esto (pseudocódigo):C operaciones # lista multiproceso

class A 
{ 
    List<SomeClass> list; 

    private void clearList() 
    { 
     list = new List<SomeClass>(); 
    } 

    private void addElement() 
    { 
     list.Add(new SomeClass(...)); 
    } 
} 

es posible que me encuentro con problemas de multiproceso (o cualquier tipo de comportamiento inesperado) cuando ambas funciones se ejecutan en paralelo?

El caso de uso es una lista de errores, que se pueden borrar en cualquier momento (simplemente asignando una nueva lista vacía).

EDIT: Mis suposiciones son

  • sólo un hilo añade elementos
  • elementos olvidados están bien (es decir, condición de carrera entre la compensación y la adición de un nuevo elemento), siempre que la operación de borrado tiene éxito sin
  • problemas
  • .NET 2,0

Respuesta

10

Hay dos posibilidades de problemas aquí:

  • Los artículos recién añadidos podrían terminar siendo olvidados de inmediato, ya que se borran y se crea una nueva lista. ¿Es eso un problema? Básicamente, si se llaman al mismo tiempo AddElement y ClearList, tiene una condición de carrera: o bien el elemento terminará en la nueva lista, o en la antigua (olvidada).
  • List<T> no es seguro para la mutación multi-hilo, por lo que si dos hilos diferentes llaman AddElement al mismo tiempo, los resultados no están garantizados

Dado que usted está accediendo a un recurso compartido, yo personalmente mantener un bloqueo mientras se accede a él. Sin embargo, aún deberá considerar la posibilidad de borrar la lista inmediatamente antes/después de agregar un elemento.

EDIT: Mi comentario acerca de que sea bien si sólo se va a añadir a partir de un hilo ya estaba un tanto dudosa, por dos razones:

  • Es posible (creo!) Que podría terminar tratando de agregar a un List<T> que aún no se había construido por completo. No estoy seguro, y el modelo de memoria .NET 2.0 (en comparación con el de la especificación ECMA) puede ser lo suficientemente fuerte para evitar eso, pero es difícil de decir.
  • Es posible que el subproceso de adición no "vea" el cambio a la variable list inmediatamente, y continúe agregando a la lista anterior. De hecho, sin ninguna sincronización, se podía ver el valor antiguo para siempre

Cuando se agrega "la iteración en la GUI" en la mezcla se pone muy difícil - porque no se puede cambiar la lista mientras estás iterando La solución más simple para esto es probablemente para proporcionar un método que devuelve una copia de la lista, y la interfaz de usuario se pueden repetir con seguridad sobre que:

class A 
{ 
    private List<SomeClass> list; 
    private readonly object listLock = new object(); 

    private void ClearList() 
    { 
     lock (listLock) 
     { 
      list = new List<SomeClass>(); 
     } 
    } 

    private void AddElement() 
    { 
     lock (listLock) 
     { 
      list.Add(new SomeClass(...)); 
     } 
    } 

    private List<SomeClass> CopyList() 
    { 
     lock (listLock) 
     { 
      return new List<SomeClass>(list); 
     } 
    } 

} 
+0

Solo un hilo está agregando elementos, por lo que el segundo punto no es un problema para mí. Y estaba al tanto de la condición de carrera, pero eso no es realmente importante para mí, pero no estoy llamando 'Clear' en absoluto, sino que estoy creando una nueva lista. ¿Sería un problema llamar 'Clear' y' Add' al mismo tiempo (dijiste 'List' no es seguro para subprocesos)? – AndiDog

+0

Lo siento, quise decir AddElement y ClearList. Solo crear una nueva lista debería estar bien, y si solo está agregando desde un solo hilo, debería estar bien. No olvide que puede haber más complicaciones cuando también está leyendo de la lista: ¿eso solo ocurriría en el mismo hilo que está haciendo el Add? –

+0

No, otro subproceso de GUI debe poder iterar sobre él. Eso se implementará en unas pocas semanas. ¿Esto puede causar problemas si la GUI usa 'foreach (Element e in instanceOfC.list) {...}' - si asigno una nueva 'List' durante esta iteración, la GUI aún funcionará en la lista anterior, ¿no? – AndiDog

2

Sí - es posible ,. De hecho, si realmente se llaman al mismo tiempo, es muy probable.

Además, también es probable que cause problemas si se producen dos llamadas separadas a addElement al mismo tiempo.

Para este tipo de subprocesamiento múltiple, realmente necesita algún tipo de bloqueo mutuamente exclusivo alrededor de la lista, de modo que solo se puede invocar una operación en la lista subyacente a la vez.

Una estrategia de bloqueo crudo alrededor de esto ayudaría. Algo como:

class A 
{ 
    static object myLock = new object() 
    List<SomeClass> list; 

    private void clearList() 
    { 
     lock(myLock) 
     { 
      list = new List<SomeClass>(); 
     } 

    } 

    private void addElement() 
    { 
     lock(myLock) 
     { 
      list.Add(new SomeClass(...)); 
     } 
    } 
} 
1

No es bueno hacer una nueva lista cuando quiere borrarla.

Supongo que también ha asignado una lista en el constructor para que no se encuentre con una excepción de puntero nulo.

Si borra y los elementos se agregan, se pueden agregar a la lista anterior, que supongo que está bien? PERO si se agregan dos elementos al mismo tiempo, puede tener problemas.

mirar en .Net 4 nuevas colecciones para manejar tareas multithreading :)

ADEMÁS: mirada hacia el espacio de nombres System.Collections.Concurrent si utiliza .Net 4. Allí encontrará: System.Collections.Concurrent.ConcurrentBag<T> y muchos otras colecciones agradables :)

También debe tener en cuenta que el bloqueo puede reducir significativamente el rendimiento si no tiene cuidado.

+0

Disculpe que no mencioné que estoy usando .NET 2.0, entonces ¿hay colecciones integradas seguras para hilos en .NET 2.0? ¿O tengo que usar 'lock'? – AndiDog

+0

Hhm, en lugar de indicar el bloqueo, etc. explícito Haría mi propia clase ala ThreadSafeList donde los métodos están bloqueados. Por lo tanto, no tiene que escribir el bloqueo varias veces (y quizás olvidar). –

+0

Acabo de encontrar esto: http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx pero no he visto el código por lo que compruébelo antes de usarlo :) –

1

Si utiliza una instancia de esta clase en varios hilos, sí. te encontrarás con problemas. Todas las colecciones en .Net Framework (versión 3.5 y versiones inferiores) NO son seguras para subprocesos. Especialmente cuando comienzas a cambiar la colección mientras otro hilo está iterando sobre ella.

Use el bloqueo y distribuya colecciones de'copias 'en entornos multiproceso, o si puede usar .Net 4.0, use las nuevas colecciones simultáneas.

0

Está claro por las ediciones de su pregunta que realmente no se preocupan por los culpables habituales aquí, en realidad no hay llamadas simultáneas a los métodos del mismo objeto.

Esencialmente usted está preguntando si está bien asignar la referencia a su lista mientras se está accediendo desde una secuencia paralela.

Por lo que yo entiendo, todavía puede causar problemas. Todo depende de cómo se implemente la asignación de referencia en el nivel de hardware. Para ser más precisos si esta operación es atómica o no.

Creo que, aunque sea tan delicado, todavía existe la posibilidad, especialmente en entornos de multiprocesador, de que el proceso se corrompa de referencia porque solo se actualizó parcialmente cuando se accedía a él.

+0

No creo que las diferencias de hardware/asignación de referencia puedan causar problemas aquí. Por lo que entiendo las especificaciones de C# (Sección 5.5: http://msdn.microsoft.com/en-us/library/aa691278%28VS.71%29.aspx), la asignación de referencia será atómica. – AndiDog

2

Las colecciones en .NET (hasta 3.5) no son seguras para hilos o no bloquean (ejecución paralela). Debe implementar el suyo derivando de IList y usar un ReaderWriterLockSlim para realizar cada acción. Por ejemplo, su método Add debe verse así:

public void Add(T item) 
    { 
     _readerWriterLockSlim.EnterWriteLock(); 
     try { _actualList.Add(item); } 
     finally { _readerWriterLockSlim.ExitWriteLock(); } 
    } 

Debe tener en cuenta algunos trucos de concurrencia aquí. Por ejemplo, debe tener un GetEnumerator que devuelva una nueva instancia como IList; no la lista real.De lo contrario, te encontrarás con problemas; que debería verse como:

public IEnumerator<T> GetEnumerator() 
    { 
     List<T> localList; 

     _lock.EnterReadLock(); 
     try { localList= new List<T>(_actualList); } 
     finally { _lock.ExitReadLock(); } 

     foreach (T item in localList) yield return item; 
    } 

y:

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return ((IEnumerable<T>)this).GetEnumerator(); 
    } 

Nota: En la aplicación de las colecciones Hilo de seguridad o en paralelo (y de hecho todas las demás clases) no se derivan de la categoría, pero INTERFACE! Porque siempre habrá problemas relacionados con la estructura interna de esa clase o con algunos métodos que no son virtuales y deberá ocultarlos, etc. Si tiene que hacer esto, ¡hágalo con mucho cuidado!