Yo estaba encantado de ver a la nueva System.Collections.Concurrent espacio de nombres en el .Net 4.0, muy agradable! He visto ConcurrentDictionary, ConcurrentQueue, ConcurrentStack, ConcurrentBag y BlockingCollection.

Una cosa que parece ser misteriosamente falta es un ConcurrentList<T>. ¿Debo escribir que yo (o salirse de la web 🙂 )?

Que me estoy perdiendo algo que es obvio aquí?

  • ConcurrentBag<T> (msdn.microsoft.com/pt-br/library/…)
  • ConcurrentBag<T> es una colección desordenada, mientras que la Lista de<T> es ordenado.
  • ¿Cómo puede una colección ordenada en un entorno multiproceso? Usted no tiene el control de la secuencia de elementos, por diseño.
  • El uso de un Bloqueo en su lugar
InformationsquelleAutor Alan | 2011-07-06

11 Comentarios

  1. 162

    Me lo intenté hace un tiempo (también: en GitHub). Mi aplicación tenido algunos problemas, que no voy a entrar aquí. Déjame decirte, lo que es más importante, lo que he aprendido.

    En primer lugar, no hay manera de que usted va a obtener una plena aplicación de IList<T> que es lockless y subprocesos. En particular, al azar de las inserciones y eliminaciones se no ir a trabajar, a menos que usted también olvidarse de O(1) de acceso aleatorio (es decir, a menos que usted «engañar» y sólo tiene que utilizar algún tipo de lista enlazada y permitir que la indización chupar).

    Lo que pensamiento podría ser interesante que era un hilo de seguridad, subconjunto limitado de IList<T>: en particular, uno que permitiría a un Add y proporcionar al azar sólo lectura acceso por índice (pero no Insert, RemoveAt, etc., y también no al azar escribir de acceso).

    Este era el objetivo de mi ConcurrentList<T> aplicación. Pero cuando he probado su rendimiento en los escenarios multiproceso, me encontré con que simplemente sincronizar añade a un List<T> fue más rápido. Básicamente, la adición de una List<T> es rápido como un rayo ya; la complejidad de los computacional pasos involucrados es minúsculo (incremento de un índice y asignar a un elemento de una matriz; que realmente es). Se necesitaría un tonelada de concurrentes escribe para ver cualquier tipo de bloqueo de contención en esto; y aun entonces, el promedio de rendimiento de cada escritura aún vencería el más caro aunque lockless aplicación en ConcurrentList<T>.

    En la relativamente raro caso de que la lista interna de la matriz de necesidades para cambiar el tamaño de sí mismo, usted tiene que pagar un pequeño costo. Así que al final llegué a la conclusión de que este era el uno nicho escenario donde un complemento sólo ConcurrentList<T> tipo de colección tendría sentido: cuando se quiere garantizado de carga baja de la incorporación de un elemento en cada llamada (así, en contraposición a una amortizado objetivo de rendimiento).

    Simplemente no es casi tan útil una clase de como se podría pensar.

    • Y si necesitas algo similar a List<T> que utiliza a la vieja usanza, monitor basado en la sincronización, hay SynchronizedCollection<T> escondido en la BCL: msdn.microsoft.com/en-us/library/ms668265.aspx
    • Una pequeña adición: el uso de la Capacidad parámetro del constructor para evitar tanto como sea posible) el tamaño de escenario.
    • Uno, además de que me gustaría añadir. Usted puede evitar el cambio de tamaño de problema haciendo clic en un vínculo de la lista de los «trozos». Por supuesto, esto añade una gran cantidad de complejidad, pero permite relativamente cerca de O(1) búsquedas y adiciones, y también se puede hacer de modo que usted no tiene que bloquear toda la estructura de un Insert o RemoveAt.. Pero de nuevo, es enormemente más complejo de implementar y nunca va a satisfacer el rendimiento de la Lista de<T>
    • El mayor escenario donde un ConcurrentList sería una victoria sería cuando no hay un montón de actividad agregando a la lista, pero hay muchos de los concurrentes a los lectores. Uno podría reducir los lectores de’ la sobrecarga de una sola memoria de barrera (y eliminar incluso que si los lectores no estaban preocupados acerca ligeramente rancio de datos).
    • Puede realizar la sincronización para que el escenario utilizando un ReaderWriterLock msdn.microsoft.com/en-us/library/…
    • Es bastante trivial para la construcción de un ConcurrentList<T> de tal manera que los lectores están garantizados para ver estado coherente, sin necesidad de tener ningún tipo de bloqueo, con relativamente leve agregado sobrecarga. Cuando la lista se expande, por ejemplo, el tamaño de 32 a 64, mantener el tamaño-32 matriz y crear un nuevo tamaño-64 matriz. Cuando la adición de cada uno de los 32 artículos, ponerlo en la ranura de un 32-63 de la nueva matriz y la copia de un antiguo elemento de la talla 32 de la matriz de la nueva. Hasta que el elemento de 64 se agrega, los lectores podrán buscar en el tamaño-32 matriz de elementos de 0 a 31 y en el tamaño de 64 matriz de elementos 32-63.
    • Una vez que el elemento de 64, se añade, el tamaño-32 matriz seguirá trabajando para obtener los elementos de 0 a 31, pero los lectores ya no necesite usarlo. Se puede utilizar el tamaño de 64 matriz para todos los elementos 0-63, y un tamaño de 128 matriz de elementos 64-127. La sobrecarga de la selección que uno de los dos matrices a utilizar, además de una barrera de memoria si lo desea, sería menos que la sobrecarga de hasta el más eficiente lector-bloqueo de escritor que se pueda imaginar. Escribe probablemente debería usar cerraduras (libre de bloqueo sería posible, sobre todo si uno no mente la creación de una nueva instancia de objeto con cada inserción, pero la cerradura debe ser barato.
    • Re: ConcurrentList would be a win would be when there isn't a whole lot of activity adding to the list, but there are many concurrent readers y especially if one didn't mind creating a new object instance with every insertion. Exactamente para casos como este, que he tenido que lidiar con la cada vez más a menudo, recientemente, el CopyOnWriteSwapper en mi respuesta es una solución genérica que realiza locklessly rápido para que todas las lecturas y las iteraciones, y permite el uso arbitrario de la estructura de datos, es decir, usted puede mantener su O(1) índice de búsqueda.
    • El CopyOnWriteSwapper implementaciones que he visto en general tiene O(N^2) comportamiento para la construcción. El ConcurrentList que me hubiera gustado haber visto hubiera retenido amortizado constante Add tiempo. El edificio no podría haber sido tan rápido como la construcción de una sincronizado lista, pero lee evitar la sincronización de sobrecarga.
    • El que yo proporcione siguiente genérico en ese sentido. El costo en mi ejemplo new List(existing) sería O(n). No es tan malo como O(n^2), pero lo suficientemente malo si la lista es enorme, o de modificar a menudo. Si usted necesita lockless acceso de lectura Y algo así como O(log n) de rendimiento para las adiciones, supongo que Inmutable que tiene que ser.
    • En mi humilde opinión, .NETO habría beneficiado en gran medida de «agregar» sólo diccionario y de lista de tipos, ya que ese tipo sólo se necesita puede ser fácilmente escritas para permitir el bloqueo de libre acceso de lectura y O(1) el acceso de escritura; además, los diccionarios de este tipo puede barata garantía que los elementos que se enumeran en el orden en que fueron añadidos (si una enumeración se inicia sólo cuando se agregan elementos, el comportamiento sea coherente con los elementos de haber sido añadido antes o después de la enumeración). Si no es necesario en realidad quitar elementos de una colección, tipos que no necesitan el apoyo de eliminación podría…
    • …proporcionar fácilmente útil garantías (siempre estado coherente para la lectura, y en el orden de enumeración) que no puede ser fácilmente satisfecho en las colecciones que permiten eliminaciones.
    • para lo que vale, ConcurrentDictionary es ahora de bloqueo gratuito de lecturas, realiza esencialmente la misma como un diccionario para las búsquedas y permite de manera segura enumeración mientras está siendo modificado.
    • No creo que ConcurrentDictionary ofrece a cualquier particular garantías acerca de la enumeración de la orden. De lo contrario, sin embargo, parece como una multa de clase. Agregar sólo las listas y los diccionarios deberían incluir un miembro no está presente en la actual interfaces; AddAndGetIndex habría que añadir un elemento y devolver su pedido de inserción, y para los diccionarios también debe ser un GetItemIndex para obtener una orden de inserción por clave. Una inmutable contenedor en un complemento único diccionario con tal función podría ser simplemente ajustar el diccionario original junto con un recuento de cuántos elementos se encontraban en cuando envuelto…
    • …y tratamiento de cualquier de los elementos añadidos después de eso, a pesar de que no existen.
    • Eso es correcto, aunque creo que lo que hay es suficiente para la mayoría de los usos de un concurrente diccionario, incluso de sólo lectura de los escenarios. Sólo por curiosidad, ¿qué escenario de uso se requieren para garantizar una enumeración de orden en un diccionario? Si quieres un complemento sólo diccionario con «instantánea» de la enumeración de comportamiento y 100% diccionario de rendimiento de lectura que he hecho una aplicación de la que aquí: singulink.com/CodeIndex/post/…
    • Un uso típico sería la situación de un vivir informe de estado de elementos únicos observado hasta ahora. Uno podría utilizar un concurrente diccionario para establecer qué elementos son únicos y, a continuación, agregarlos a una lista concurrente, pero debería ser posible tener una estructura de datos servir para ambos propósitos.

  2. 37

    Lo que usted utilizaría un ConcurrentList para?

    El concepto de Acceso Aleatorio contenedor en una rosca mundo no es tan útil como puede parecer. La declaración de

      if (i < MyConcurrentList.Count)  
          x = MyConcurrentList[i]; 

    como un todo, aún no se thread-safe.

    Lugar de crear un ConcurrentList, tratar de construir soluciones con lo que hay. La mayoría de las clases más comunes son la ConcurrentBag y especialmente la BlockingCollection.

    • Buen punto. Todavía lo que estoy haciendo es un poco más mundano. Solo estoy tratando de asignar el ConcurrentBag<T> en un IList<T>. Yo podría cambiar mi propiedad a un IEnumerable<T>, pero luego no puedo .Agregar cosas a ella.
    • No sigo su argumento. ¿Por qué no tienen el mismo tipo de problema con if (!dict.ContainsKey(somekey)) dict[someKey] = someValue. Habrá hilo de las cuestiones de seguridad con código (por ejemplo, el uso de ConcurrentDictionary)?
    • Java tiene el concepto de un ConcurrentList: download.oracle.com/javase/6/docs/api/java/util/concurrent/…
    • No hay forma de implementar que sin el bloqueo de la lista. Desde ya se puede utilizar Monitor hacerlo de todos modos, no hay razón para que una lista concurrente.
    • Usted podría utilizar TryGetValue() en su mayoría.
    • No veo uno. Hay una copia en versión escrita, pero eso no es lo que la mayoría de la gente esperaría de un «concurrentes» colección de hacer.
    • Ok, pero, ¿por qué todavía tienen la ConcurrentDictionary.ContainsKey método?
    • el punto de un ConcurrentList sería consuelo a todos nosotros de la carga de bloqueo/desbloqueo. Misma razón que todos los demás concurrentes existen colecciones.
    • sí, esta intrínsecamente no-thread-safe. ConcurrentDictionary tiene métodos especiales que hacer esto en una operación atómica, como AddOrUpdate, GetOrAdd, TryUpdate, etc. Ellos todavía tienen ContainsKey, porque a veces sólo quiero saber si la clave está ahí, sin modificar el diccionario (creo HashSet)
    • sí, pero ese es mi punto. Me parece raro que no es thread-safe métodos en una clase cuyo nombre comienza con la palabra «Concurrentes». Pero yo veo lo que estás diciendo, gracias por las aclaraciones y explicaciones.
    • ContainsKey es seguro para subprocesos por sí mismo, de su ejemplo (no ContainsKey!) sólo tiene una condición de carrera, ya que hacer una segunda llamada, dependiendo de la primera decisión, que en ese momento ya estar fuera de fecha.
    • Henk, no estoy de acuerdo. Creo que no es un simple escenario en el que podría ser muy útil. Subproceso de trabajo de escritura en que se subproceso de interfaz de usuario de lectura y actualización de la interfaz en consecuencia. Si desea añadir elemento en una ordenados de la moda, se requerirá de acceso aleatorio escribir. También se puede utilizar una pila y una vista de los datos, pero usted tendrá que mantener 2 colecciones :-(.

  3. 18

    Con todo el respeto debido a la gran respuesta proporcionada ya que hay veces que simplemente quiero un hilo de seguridad IList. Nada de avanzada o de fantasía. El rendimiento es importante, en muchos casos, pero a veces eso no es una preocupación. Sí, no siempre van a ser los retos sin métodos como el de «TryGetValue», etc, pero la mayoría de los casos sólo quiero algo que puedo enumerar sin necesidad de preocuparse por poner bloqueos alrededor de todo. Y sí, alguien puede probablemente encontrar algún «bug» en mi aplicación que podría conducir a un callejón sin salida o algo (supongo), pero vamos a ser honestos: Cuando se trata de multi-threading, si no escribir el código correctamente, va de interbloqueo de todos modos. Con esto en mente, decidí hacer una simple ConcurrentList aplicación que proporciona a estas necesidades básicas.

    Y por lo que vale la pena: hice una prueba básica de la adición de 10.000.000 de elementos de Lista normal y ConcurrentList y los resultados fueron:

    Lista terminada en: 7793 milisegundos.
    Concurrente terminado en: 8064 milisegundos.

    public class ConcurrentList<T> : IList<T>, IDisposable
    {
    #region Fields
    private readonly List<T> _list;
    private readonly ReaderWriterLockSlim _lock;
    #endregion
    #region Constructors
    public ConcurrentList()
    {
    this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
    this._list = new List<T>();
    }
    public ConcurrentList(int capacity)
    {
    this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
    this._list = new List<T>(capacity);
    }
    public ConcurrentList(IEnumerable<T> items)
    {
    this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
    this._list = new List<T>(items);
    }
    #endregion
    #region Methods
    public void Add(T item)
    {
    try
    {
    this._lock.EnterWriteLock();
    this._list.Add(item);
    }
    finally
    {
    this._lock.ExitWriteLock();
    }
    }
    public void Insert(int index, T item)
    {
    try
    {
    this._lock.EnterWriteLock();
    this._list.Insert(index, item);
    }
    finally
    {
    this._lock.ExitWriteLock();
    }
    }
    public bool Remove(T item)
    {
    try
    {
    this._lock.EnterWriteLock();
    return this._list.Remove(item);
    }
    finally
    {
    this._lock.ExitWriteLock();
    }
    }
    public void RemoveAt(int index)
    {
    try
    {
    this._lock.EnterWriteLock();
    this._list.RemoveAt(index);
    }
    finally
    {
    this._lock.ExitWriteLock();
    }
    }
    public int IndexOf(T item)
    {
    try
    {
    this._lock.EnterReadLock();
    return this._list.IndexOf(item);
    }
    finally
    {
    this._lock.ExitReadLock();
    }
    }
    public void Clear()
    {
    try
    {
    this._lock.EnterWriteLock();
    this._list.Clear();
    }
    finally
    {
    this._lock.ExitWriteLock();
    }
    }
    public bool Contains(T item)
    {
    try
    {
    this._lock.EnterReadLock();
    return this._list.Contains(item);
    }
    finally
    {
    this._lock.ExitReadLock();
    }
    }
    public void CopyTo(T[] array, int arrayIndex)
    {
    try
    {
    this._lock.EnterReadLock();
    this._list.CopyTo(array, arrayIndex);
    }
    finally
    {
    this._lock.ExitReadLock();
    }
    }
    public IEnumerator<T> GetEnumerator()
    {
    return new ConcurrentEnumerator<T>(this._list, this._lock);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
    return new ConcurrentEnumerator<T>(this._list, this._lock);
    }
    ~ConcurrentList()
    {
    this.Dispose(false);
    }
    public void Dispose()
    {
    this.Dispose(true);
    }
    private void Dispose(bool disposing)
    {
    if (disposing)
    GC.SuppressFinalize(this);
    this._lock.Dispose();
    }
    #endregion
    #region Properties
    public T this[int index]
    {
    get
    {
    try
    {
    this._lock.EnterReadLock();
    return this._list[index];
    }
    finally
    {
    this._lock.ExitReadLock();
    }
    }
    set
    {
    try
    {
    this._lock.EnterWriteLock();
    this._list[index] = value;
    }
    finally
    {
    this._lock.ExitWriteLock();
    }
    }
    }
    public int Count
    {
    get
    {
    try
    {
    this._lock.EnterReadLock();
    return this._list.Count;
    }
    finally
    {
    this._lock.ExitReadLock();
    }
    }
    }
    public bool IsReadOnly
    {
    get { return false; }
    }
    #endregion
    }
    public class ConcurrentEnumerator<T> : IEnumerator<T>
    {
    #region Fields
    private readonly IEnumerator<T> _inner;
    private readonly ReaderWriterLockSlim _lock;
    #endregion
    #region Constructor
    public ConcurrentEnumerator(IEnumerable<T> inner, ReaderWriterLockSlim @lock)
    {
    this._lock = @lock;
    this._lock.EnterReadLock();
    this._inner = inner.GetEnumerator();
    }
    #endregion
    #region Methods
    public bool MoveNext()
    {
    return _inner.MoveNext();
    }
    public void Reset()
    {
    _inner.Reset();
    }
    public void Dispose()
    {
    this._lock.ExitReadLock();
    }
    #endregion
    #region Properties
    public T Current
    {
    get { return _inner.Current; }
    }
    object IEnumerator.Current
    {
    get { return _inner.Current; }
    }
    #endregion
    }
    • OK, respuesta anterior pero aún así: RemoveAt(int index) nunca es thread-safe, Insert(int index, T item) sólo es seguro para el índice de==0, el retorno de IndexOf() es inmediatamente obsoleta etc. Ni siquiera empezar acerca de la this[int].
    • Y usted no necesita y no quiere que un ~Finalizador().
    • Usted dice que usted ha dado en la prevención de la posibilidad de interbloqueo – y una sola ReaderWriterLockSlim puede ser hecho a un punto muerto fácil mediante el uso de EnterUpgradeableReadLock() al mismo tiempo. No obstante, no hacer que el bloqueo sea accesible para el exterior, y no por ejemplo llamar a un método que entra en un bloqueo de escritura mientras se mantiene un bloqueo de lectura, por lo que la utilización de su clase no hacer interbloqueos más probable.
    • Los no concurrentes de la interfaz no es apropiado para el acceso concurrente. E. g. la siguiente no es atómico var l = new ConcurrentList<string>(); /* ... */ l[0] += "asdf";. En general, cualquier lectura-escritura combo puede conducir a serios problemas cuando se realiza de forma concurrente. Es por eso simultáneas de las estructuras de datos generalmente proporcionan métodos para aquellos que, como ConcurrentDictionary‘s AddOrGet etc. N. B. la constante (y redundante debido a que los miembros ya están marcados como tales por el carácter de subrayado) repetición de this. interferencias.
    • Gracias Eugenio. Yo soy un gran usuario de .NET Reflector que pone «esto». en todos los no-campos estáticos. Como tal, he llegado a preferir la misma. Con respecto a esto no concurrentes de la interfaz de no ser apropiadas: Usted tiene toda la razón, tratando de realizar varias acciones en contra de mi aplicación puede no ser fiable. Pero el requisito aquí es, simplemente, que solo acciones (agregar, o eliminar, o claro, o enumeración) se puede hacer sin dañar la colección. Básicamente elimina la necesidad de poner bloqueo de declaraciones en torno a todo.
  4. 11

    ConcurrentList (como resizeable matriz, no una lista enlazada) no es fácil escribir con bloqueos de las operaciones. Su API no se traduce bien a un «concurrentes» versión.

    • No sólo es difícil de escribir, es incluso difícil encontrar una interfaz útil.
  5. 10

    La razón por la que no hay ConcurrentList es porque fundamentalmente no puede ser escrito. La razón de ello es que varias operaciones importantes en IList se basan en los índices bursátiles, y que simplemente no funciona. Por ejemplo:

    int catIndex = list.IndexOf("cat");
    list.Insert(catIndex, "dog");

    El efecto que el autor se va después de que se inserte «perro» antes de «gato», pero en un entorno multiproceso, cualquier cosa puede pasar a la lista entre esas dos líneas de código. Por ejemplo, otro hilo podría hacer list.RemoveAt(0), cambiando toda la lista a la izquierda, pero, fundamentalmente, catIndex no va a cambiar. El impacto es que el Insert operación se pone el «perro» después de el gato, no antes.

    Las diversas implementaciones que se ofrece como «respuestas» a esta pregunta son bien intencionados, pero como la anterior muestra, que no ofrecen resultados fiables. Si usted realmente desea una lista como la semántica en un entorno multiproceso, no se puede llegar allí por poner cerrojos dentro de la lista de métodos de ejecución. Usted tiene que asegurarse de que cualquier índice de uso vidas totalmente dentro del contexto de la cerradura. El resultado de todo esto es que usted puede utilizar una Lista en un entorno multiproceso con el derecho de bloqueo, pero la lista en sí no puede ser hecho para existir en el mundo.

    Si usted piensa que necesita una lista concurrente, en realidad, hay sólo dos posibilidades:

    1. Lo que realmente necesita es un ConcurrentBag
    2. Usted necesita para crear su propia colección, tal vez implementado con una Lista y su propio control de concurrencia.

    Si usted tiene un ConcurrentBag y están en una posición donde usted necesita para pasar como un IList, entonces usted tiene un problema, porque el método que está llamando, ha indicado que se podría intentar hacer algo como lo hice anteriormente con el cat & perro. En la mayoría de los mundos, lo que significa es que el método que está llamando simplemente no está construido para trabajar en un entorno multiproceso. Eso significa que usted refactorizar es lo que es o, si no, vas a tener que manejar con mucho cuidado. Usted es casi seguro que se requiere para crear su propia colección con sus propios candados, y llamar al método infractor dentro de un bloqueo.

  6. 5

    En los casos donde lee mucho más numerosos que escribe, o (sin embargo frecuente) escribe no son simultáneas, un copy-on-write enfoque puede ser apropiado.

    La implementación que se muestra a continuación es

    • lockless
    • extraordinariamente rápido para lecturas simultáneas, incluso mientras concurrente de las modificaciones en curso – no importa el tiempo que tome
    • porque «instantáneas» son inmutables, lockless atomicidad es posible, es decir, var snap = _list; snap[snap.Count - 1]; nunca (bueno, excepto por una lista vacía de curso) tiro, y usted también consigue el hilo de seguridad de la enumeración con una semántica de instantáneas de forma gratuita.. cómo me gusta la inmutabilidad!
    • aplicado genéricamente, aplicable a cualquier estructura de datos y cualquier tipo de modificación
    • muertos simple, es decir, fáciles de probar, depurar, comprobar por la lectura de el código
    • utilizable en .Net 3.5

    De copia en escritura para el trabajo, usted tiene que mantener sus estructuras de datos efectivamente inmutable, es decir, no se puede cambiar después de que puso a disposición de los otros hilos. Cuando se desea modificar, se

    1. clon de la estructura
    2. hacer modificaciones en el clon
    3. atómicamente de intercambio en la referencia a la modificación de clon

    Código

    static class CopyOnWriteSwapper
    {
    public static void Swap<T>(ref T obj, Func<T, T> cloner, Action<T> op)
    where T : class
    {
    while (true)
    {
    var objBefore = Volatile.Read(ref obj);
    var newObj = cloner(objBefore);
    op(newObj);
    if (Interlocked.CompareExchange(ref obj, newObj, objBefore) == objBefore)
    return;
    }
    }
    }

    Uso

    CopyOnWriteSwapper.Swap(ref _myList,
    orig => new List<string>(orig),
    clone => clone.Add("asdf"));

    Si necesita más rendimiento, que le ayudará a ungenerify el método, por ejemplo, crear un método para cada tipo de modificación (Añadir, Eliminar, …) que desea, y el código de la función punteros cloner y op.

    N. B. #1 es su responsabilidad asegurarse de que el no modifica el (supuestamente) inmutable estructura de datos. No hay nada que podamos hacer en un genérico aplicación para evitar eso, pero cuando especializada para List<T>, usted podría guardia en contra de la modificación mediante Lista.AsReadOnly()

    N. B. #2 Ser cuidadoso acerca de los valores en la lista. La copia en escritura enfoque por encima de los guardias de su pertenencia a una lista única, pero si quieres poner no cadenas, pero algunos otros objetos mutables en allí, usted tiene que cuidar de la seguridad de los subprocesos (por ejemplo, bloqueo). Pero que es ortogonal a esta solución y, por ejemplo, el bloqueo de la mutable valores pueden ser fácilmente utilizado sin problemas. Usted sólo necesita ser consciente de ello.

    N. B. #3 Si su estructura de datos es enorme y se modifica con frecuencia, la copia-todo-sobre-escribir el método podría ser prohibitivo en términos de consumo de memoria y la CPU costo de las copias de los involucrados. En ese caso, es posible que desee utilizar MS Las Colecciones Inmutables lugar.

  7. 3

    System.Collections.Generic.List<t> ya es seguro para subprocesos múltiples lectores. Tratando de hacer hilo de seguro para varios escritores no tendría sentido. (Por razones de Henk y Esteban ya se ha mencionado)

    • Usted no puede ver un escenario en el que yo podría tener 5 hilos añadir a la lista? De esta manera usted puede ver la lista de acumular registros, incluso antes de que todo termine.
    • que sería un ConcurrentQueue, ConcurrentStack o ConcurrentBag. Para hacer sentido de una ConcurrentList usted debe proveer de un caso de uso donde las clases disponibles no son suficientes. No veo por qué me gustaría acceso indizado cuando los elementos en los índices pueden cambiar aleatoriamente a través de concurrentes quita. Y para un «bloqueado» leer ya se puede tomar instantáneas de la existente concurrente de las clases y los puso en una lista.
    • Tienes razón, yo no quiero acceso indizado. Yo generalmente uso IList<T> como un proxy para un IEnumerable a la que puedo .Add(T) nuevos elementos. Ahí es donde surge la pregunta, realmente.
    • Entonces usted quiere una cola, no una lista.
    • Creo que usted está equivocado. Diciendo: seguro para varios lectores no significa que usted no puede escribir en el mismo tiempo. Escribir también significaría eliminar y aparecerá un mensaje de error si se elimina, mientras que la iteración en ella.
    • Nunca me dijo que se podía escribir al mismo tiempo. Usted puede tener un escritor. O usted puede tener varios lectores. No tanto.
    • Nunca me dijo que era una «lista concurrente». No hay tal cosa como «thread safe». Hilo de seguridad requiere de un entendimiento de la real hilo características de seguridad de una estructura. No todos los datos de la estructura es segura para varios lectores y escritores del no, porque muchas de las estructuras de datos globales compartidos estado no decirle a usted acerca de.
    • Que es exactamente lo que mi respuesta dice. Entiendo que la pregunta para ello; pero lo que pide la pregunta fundamentalmente no tiene ningún sentido.
    • Bueno, creo que voy a borrar mis comentarios, ya que no añade mucho – excepto para el desorden – a Eric Quellet comentario, con el que estoy de acuerdo, aunque «nunca se dijo que» es. Indicando explícitamente lo que dijo Eric en su respuesta, y quizás agregando que sólo tiene sentido para escribir-en-construcción de escenarios de sólo sería útil y evitar mayores de Eric y Eugene.

  8. 2

    Algunas personas hilighted algunos bienes puntos (y algunos de mis pensamientos):

    • Podría looklikes loco no aleatorio accesser (alimentador) pero a mí me parece bien. Usted sólo tiene que pensar que hay muchos métodos multi-threaded colecciones que podría fallar como Indexador y Eliminar. También se podría definir fallo (reserva) a la acción de escribir descriptor de acceso como «fail» o simplemente «añadir al final».
    • No es porque es un multiproceso colección que siempre va a ser utilizado en un multiproceso contexto. O también podría ser utilizado por un solo escritor y un lector.
    • Otra manera de ser capaz de utilizar el indexador de una manera segura podría ser la envoltura de acciones en un bloqueo de la colección utilizando su raíz (en caso de hacerse pública).
    • Para muchas personas, hacer una llave raíz visible va contra el minimum de «Buenas prácticas». No estoy 100% seguro sobre este punto, porque si está oculto quitar una gran cantidad de flexibilidad para el usuario. Siempre tenemos que recordar que la programación multithread no es para cualquiera. No podemos evitar cualquier tipo de mal uso.
    • Microsoft tendrá que hacer algo de trabajo y definir un nuevo estándar para introducir el uso adecuado de Multiproceso colección. Primero el IEnumerator no debería tener un moveNext, pero debe haber un GetNext que devolver verdadero o falso, y obtener un parámetro de tipo T (de esta manera la iteración no sería el bloqueo de más). Además, Microsoft ya el uso de «uso» internamente en el foreach, pero a veces el uso de la IEnumerator directamente sin envolver con «uso» (un error en vista de colección y, probablemente, en más lugares) – Envolver el uso de IEnumerator es una práctica recomendada por Microsoft. Este error quitar buen potencial para la seguridad de la iterador… Iterador de bloqueo de la colección en el constructor y desbloqueo en su método Dispose – para un bloqueo de método foreach.

    Que no es una respuesta. Esta es sólo comentarios que no encaja realmente en un lugar específico.

    … Mi conclusión, Microsoft tiene que hacer algunos cambios profundos a la «foreach» para hacer Multiproceso colección más fácil de usar. También se ha de seguir sus propias reglas de IEnumerator de uso. Hasta que, podemos escribir una MultiThreadList fácilmente que el uso de un bloqueo de iterador pero que no siga «IList». En su lugar, se ha de definir en el propio «IListPersonnal» de la interfaz que podría fallar en «insertar», «eliminar» y descriptor de acceso aleatorio (alimentador) sin excepción. Pero quién va a querer usarlo si no es estándar ?

    • Uno podría fácilmente escribir un ConcurrentOrderedBag<T> que incluiría un sólo lectura implementación de IList<T>, sino que también podría ofrecer un seguro para subprocesos int Add(T value) método. No veo por qué cualquier ForEach cambios necesarios. Aunque Microsoft no explícitamente, por así decirlo, su práctica sugiere que es perfectamente aceptable para IEnumerator<T> para enumerar el contenido de la colección que existía cuando fue creada; la colección modificado excepción sólo es necesario si el enumerador sería incapaz de garantizar el fallo de la operación.
    • La iteración de un MT de colección, el diseño podría llevar, como dijo, es una excepción… Que uno no lo sé. Iba a atrapar todas las excepciones? En mi propio libro excepción es la excepción y no debe se produce en condiciones normales de ejecución de código. De lo contrario, para evitar que la excepción, usted tiene que bloquear la colección u obtener una copia (en modo seguro-es decir, de bloqueo) o implementar muy complejo mecanismo de la colección para evitar que la excepción se produce debido a la concurrencia. A mi aunque se que sería bueno añadir un IEnumeratorMT que el bloqueo de la colección, mientras que una de cada ocurrir y agregar el código relacionado…
    • La otra cosa que también puede ocurrir es que al obtener un iterador, puede bloquear la colección y cuando el iterador es GC recogida, se puede desbloquear la colección. De acuerdo a Microsoft ® ya comprobar si la interfaz IEnumerable es también un IDisposable y llamar a la GC si es así al final de un ForEach. El principal problema es que también utilizan la interfaz IEnumerable en otro lugar sin llamar a la GC, entonces usted no puede depender de eso. Tener un nuevo claro MT de interfaz para la interfaz IEnumerable habilitación del bloqueo haría resolver el problema, al menos una parte de ella. (No impediría que la gente no lo llaman).
    • Es una forma muy mala para un público GetEnumerator método para dejar una colección bloqueado después de que se devuelve; tales diseños fácilmente puede conducir a un callejón sin salida. El IEnumerable<T> no proporciona ninguna indicación de si una enumeración que se puede esperar para completar incluso si una colección es modificado; lo mejor que uno puede hacer es escribir el propio métodos para que puedan hacerlo, y han métodos que aceptan IEnumerable<T> documento el hecho de que sólo se subprocesos si el IEnumerable<T> apoya thread-safe) de la enumeración.
    • Lo que hubiera sido más útil hubiera sido si IEnumerable<T> había incluido una «Instantánea» con el método de tipo de retorno IEnumerable<T>. Las colecciones inmutables podría volver en sí; una limitada colección podría, si nada más se copia a sí mismo a un List<T> o T[] y llame a GetEnumerator en que. Algunos ilimitado de colecciones podría implementar Snapshot, y aquellos que no sería capaz de lanzar una excepción, sin tratar de llenar una lista con sus contenidos.
    • Que en su mayoría están de acuerdo. Instantánea sería muy bonito. Y de bloqueo en «GetEnumerator» podría fácilmente conducir a lo que usted dice, porque las personas no esperan que ese comportamiento. Yo no digo que tengo la solución perfecta, pero tener un iterador sin bloqueo es peligroso y la implementación debe evitar que seguro. El problema con las instantáneas de rendimiento para la gran colección. Creo que lo ideal sería que MT de colección debe ser implementado en más de una manera de acuerdo a su querido uso (principal uso). Con la instantánea (como usted dice) y también con la mecánica de la habilitación de modificación, mientras que la iteración (que ralentizan insertar/eliminar).
    • Gracias supercat por sus comentarios. Le agradezco.
    • No todas las colecciones que podría implementar Snapshot rápidamente, y algunos no pudieron ponerlo en práctica en todas, pero muchas de las colecciones se podría implementar es mucho más rápidamente de lo que podrían fuera de código, cuyo único contacto con el tipo fue a través de interfaces públicas. Por ejemplo, una instantánea de un AppendOnlyList podría encapsular una referencia a la lista y el número de elementos que contiene actualmente; aunque la lista no estaría completamente inmutable, una lista de informes de la misma como la que contiene N elementos pueden garantizar que los N primeros elementos son inmutables.
    • A la derecha! Me gusta tu idea de AppendOnlyList, se habría resuelto algunos de mis necesidades.
    • Creo que AppendOnlyList y AddOnlyDictionary habría sido muy agradable tipos, ya que puede ser barato implementado de tal manera como para permitir cualquier número de lectores de simultánea con un solo escritor, permiten la construcción de inmutable instantáneas, y (en el caso de AddOnlyDictionary), promesa para enumerar los elementos en el orden en que fueron añadidos (garantías de no hecho por Dictionary, ConcurrentDictionary, ni ConcurrentBag).
    • Totalmente de acuerdo. Me hubiera utilizarlos par de veces en el curso de mis proyectos. Voy a considerar la posibilidad de escribir la próxima vez que se necesite. Gracias.

  9. 1

    Y secuencialmente a la ejecución de código las estructuras de datos utilizadas son diferentes (bien escrito) simultáneamente a la ejecución de código. La razón es que el código secuencial implica orden implícito. Concurrente código sin embargo no implica ningún tipo de orden, mejor aún que implica la falta de cualquier orden definido!

    Debido a esto, las estructuras de datos con el orden implicado (de la Lista), no son muy útiles para la resolución concurrente de problemas. Una lista implica orden, pero que no define claramente lo que es orden. Debido a esto, el orden de ejecución del código de la manipulación de la lista de determinar (en cierta medida) el orden implícito de la lista, que está en conflicto directo con un eficiente concurrente solución.

    Recuerde que la simultaneidad es un problema de datos, no es un problema de código! No se puede Implementar el código de la primera (o reforma de las existentes secuencial código) y conseguir un diseño concurrente solución. Usted necesita para diseñar las estructuras de datos de primera, mientras que teniendo en cuenta que la ordenación implícita no existe en un sistema concurrente.

  10. 1

    lockless Copiar y Escribir enfoque funciona muy bien si usted no está tratando con demasiados elementos.
    He aquí una clase I escribió:

    public class CopyAndWriteList<T>
    {
    public static List<T> Clear(List<T> list)
    {
    var a = new List<T>(list);
    a.Clear();
    return a;
    }
    public static List<T> Add(List<T> list, T item)
    {
    var a = new List<T>(list);
    a.Add(item);
    return a;
    }
    public static List<T> RemoveAt(List<T> list, int index)
    {
    var a = new List<T>(list);
    a.RemoveAt(index);
    return a;
    }
    public static List<T> Remove(List<T> list, T item)
    {
    var a = new List<T>(list);
    a.Remove(item);
    return a;
    }
    }

    ejemplo de uso:
    orders_BUY = CopyAndWriteList.Claro(orders_BUY);

    • Usted debería dar alguna explicación a su respuesta…
    • en lugar de bloquear, crea una copia de la lista, se modifica la lista y establecer la referencia a la nueva lista. Por lo que cualquier otros hilos que son la iteración no le causará problemas.
  11. 0

    He implementado una similar Brian. La mía es diferente:

    • Me administrar la matriz directamente.
    • Yo no introduzca las cerraduras en el bloque try.
    • Yo uso yield return para la producción de un enumerador.
    • Yo apoyo el bloqueo de la recursividad. Esto permite que lee de la lista durante la iteración.
    • Yo uso actualizable bloqueos de lectura donde sea posible.
    • DoSync y GetSync métodos que permitan secuencial de las interacciones que requieren acceso exclusivo a la lista.

    El código:

    public class ConcurrentList<T> : IList<T>, IDisposable
    {
    private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    private int _count = 0;
    public int Count
    {
    get
    { 
    _lock.EnterReadLock();
    try
    {           
    return _count;
    }
    finally
    {
    _lock.ExitReadLock();
    }
    }
    }
    public int InternalArrayLength
    { 
    get
    { 
    _lock.EnterReadLock();
    try
    {           
    return _arr.Length;
    }
    finally
    {
    _lock.ExitReadLock();
    }
    }
    }
    private T[] _arr;
    public ConcurrentList(int initialCapacity)
    {
    _arr = new T[initialCapacity];
    }
    public ConcurrentList():this(4)
    { }
    public ConcurrentList(IEnumerable<T> items)
    {
    _arr = items.ToArray();
    _count = _arr.Length;
    }
    public void Add(T item)
    {
    _lock.EnterWriteLock();
    try
    {       
    var newCount = _count + 1;          
    EnsureCapacity(newCount);           
    _arr[_count] = item;
    _count = newCount;                  
    }
    finally
    {
    _lock.ExitWriteLock();
    }       
    }
    public void AddRange(IEnumerable<T> items)
    {
    if (items == null)
    throw new ArgumentNullException("items");
    _lock.EnterWriteLock();
    try
    {           
    var arr = items as T[] ?? items.ToArray();          
    var newCount = _count + arr.Length;
    EnsureCapacity(newCount);           
    Array.Copy(arr, 0, _arr, _count, arr.Length);       
    _count = newCount;
    }
    finally
    {
    _lock.ExitWriteLock();          
    }
    }
    private void EnsureCapacity(int capacity)
    {   
    if (_arr.Length >= capacity)
    return;
    int doubled;
    checked
    {
    try
    {           
    doubled = _arr.Length * 2;
    }
    catch (OverflowException)
    {
    doubled = int.MaxValue;
    }
    }
    var newLength = Math.Max(doubled, capacity);            
    Array.Resize(ref _arr, newLength);
    }
    public bool Remove(T item)
    {
    _lock.EnterUpgradeableReadLock();
    try
    {           
    var i = IndexOfInternal(item);
    if (i == -1)
    return false;
    _lock.EnterWriteLock();
    try
    {   
    RemoveAtInternal(i);
    return true;
    }
    finally
    {               
    _lock.ExitWriteLock();
    }
    }
    finally
    {           
    _lock.ExitUpgradeableReadLock();
    }
    }
    public IEnumerator<T> GetEnumerator()
    {
    _lock.EnterReadLock();
    try
    {    
    for (int i = 0; i < _count; i++)
    //deadlocking potential mitigated by lock recursion enforcement
    yield return _arr[i]; 
    }
    finally
    {           
    _lock.ExitReadLock();
    }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
    return this.GetEnumerator();
    }
    public int IndexOf(T item)
    {
    _lock.EnterReadLock();
    try
    {   
    return IndexOfInternal(item);
    }
    finally
    {
    _lock.ExitReadLock();
    }
    }
    private int IndexOfInternal(T item)
    {
    return Array.FindIndex(_arr, 0, _count, x => x.Equals(item));
    }
    public void Insert(int index, T item)
    {
    _lock.EnterUpgradeableReadLock();
    try
    {                       
    if (index > _count)
    throw new ArgumentOutOfRangeException("index"); 
    _lock.EnterWriteLock();
    try
    {       
    var newCount = _count + 1;
    EnsureCapacity(newCount);
    //shift everything right by one, starting at index
    Array.Copy(_arr, index, _arr, index + 1, _count - index);
    //insert
    _arr[index] = item;     
    _count = newCount;
    }
    finally
    {           
    _lock.ExitWriteLock();
    }
    }
    finally
    {
    _lock.ExitUpgradeableReadLock();            
    }
    }
    public void RemoveAt(int index)
    {   
    _lock.EnterUpgradeableReadLock();
    try
    {   
    if (index >= _count)
    throw new ArgumentOutOfRangeException("index");
    _lock.EnterWriteLock();
    try
    {           
    RemoveAtInternal(index);
    }
    finally
    {
    _lock.ExitWriteLock();
    }
    }
    finally
    {
    _lock.ExitUpgradeableReadLock();            
    }
    }
    private void RemoveAtInternal(int index)
    {           
    Array.Copy(_arr, index + 1, _arr, index, _count - index-1);
    _count--;
    //release last element
    Array.Clear(_arr, _count, 1);
    }
    public void Clear()
    {
    _lock.EnterWriteLock();
    try
    {        
    Array.Clear(_arr, 0, _count);
    _count = 0;
    }
    finally
    {           
    _lock.ExitWriteLock();
    }   
    }
    public bool Contains(T item)
    {
    _lock.EnterReadLock();
    try
    {   
    return IndexOfInternal(item) != -1;
    }
    finally
    {           
    _lock.ExitReadLock();
    }
    }
    public void CopyTo(T[] array, int arrayIndex)
    {       
    _lock.EnterReadLock();
    try
    {           
    if(_count > array.Length - arrayIndex)
    throw new ArgumentException("Destination array was not long enough.");
    Array.Copy(_arr, 0, array, arrayIndex, _count);
    }
    finally
    {
    _lock.ExitReadLock();           
    }
    }
    public bool IsReadOnly
    {   
    get { return false; }
    }
    public T this[int index]
    {
    get
    {
    _lock.EnterReadLock();
    try
    {           
    if (index >= _count)
    throw new ArgumentOutOfRangeException("index");
    return _arr[index]; 
    }
    finally
    {
    _lock.ExitReadLock();               
    }           
    }
    set
    {
    _lock.EnterUpgradeableReadLock();
    try
    {
    if (index >= _count)
    throw new ArgumentOutOfRangeException("index");
    _lock.EnterWriteLock();
    try
    {                       
    _arr[index] = value;
    }
    finally
    {
    _lock.ExitWriteLock();              
    }
    }
    finally
    {
    _lock.ExitUpgradeableReadLock();
    }
    }
    }
    public void DoSync(Action<ConcurrentList<T>> action)
    {
    GetSync(l =>
    {
    action(l);
    return 0;
    });
    }
    public TResult GetSync<TResult>(Func<ConcurrentList<T>,TResult> func)
    {
    _lock.EnterWriteLock();
    try
    {           
    return func(this);
    }
    finally
    {
    _lock.ExitWriteLock();
    }
    }
    public void Dispose()
    {   
    _lock.Dispose();
    }
    }
    • ¿Qué sucede si dos hilos de entrar en el comienzo de la try bloque en Remove o el indizador setter al mismo tiempo?
    • eso no parece posible. Lea comentarios en msdn.microsoft.com/en-us/library/…. Ejecutando este código, usted nunca va a entrar en que el bloqueo de una 2ª hora: gist.github.com/ronnieoverby/59b715c3676127a113c3
    • Overby: Muy Interesante. Dado que, sospecho que esto funcionaría mucho mejor si se quita la UpgradableReadLock de todas las funciones donde la única operación que se realiza en el tiempo entre la actualización de bloqueo de lectura y el bloqueo de escritura–la sobrecarga de tomar cualquier tipo de cerradura es mucho más que la comprobación para ver si el parámetro está fuera de rango que sólo haciendo que revise el interior del bloqueo de escritura será probablemente mejor.
    • Esta clase también no parece muy útil, como el desplazamiento basado en funciones (la mayoría) no puede jamás ser utilizado de forma segura a menos que exista alguna externo esquema de bloqueo de todos modos porque la recogida de cambio entre el momento de decidir dónde poner o conseguir algo y cuando usted consigue realmente es.
    • Así que no lo use.
    • Yo quería ir en el registro diciendo que he de reconocer que la utilidad de IList de la semántica en escenarios simultáneos es limitado. Escribí este código, probablemente antes de que yo llegara a esa realización. Mi experiencia es la misma que el escritor de la aceptó respuesta: me dio a probar con lo que yo sabía acerca de la sincronización y IList<T> y he aprendido algo por hacer eso.

Dejar respuesta

Please enter your comment!
Please enter your name here