2009-11-06 5 views
11

me encuentro muy intrigado por la existencia de una clase ConcurrentBag<T> en el próximo marco .NET 4.0:¿Cómo podría implementarse una clase como. ConcurrentBag <T> de .NET?

Las bolsas son útiles para almacenar objetos Al realizar el pedido, no importa, ya diferencia de conjuntos, bolsas apoyan duplicados.

Mi pregunta es: ¿cómo podría implementarse esta idea? La mayoría de las colecciones con las que estoy familiarizado equivalen esencialmente (bajo el capó) a algún tipo de matriz, en cuyo orden puede no "importarse", pero hay en una orden (por lo que, aunque no es necesario, la enumeración pasará casi siempre a través de una colección sin cambios, ya sea List, Queue, Stack, etc. en la misma secuencia).

Si tuviera que adivinar, podría sugerir que internamente podría ser Dictionary<T, LinkedList<T>>; pero eso en realidad parece bastante dudoso considerando que no tendría sentido usar solo tipo T como clave.

Lo que estoy esperando/esperando es que este sea en realidad un tipo de objeto establecido que ya ha sido "descifrado" en alguna parte, y que alguien que conozca este tipo establecido pueda contarme al respecto. Es tan inusual para mí, uno de esos conceptos que es fácil de entender en la vida real, pero que es difícil de traducir en una clase utilizable como desarrollador, y por eso tengo curiosidad sobre las posibilidades.

EDITAR:

Algunos respondedores han sugerido que un Bag podría ser una forma de una tabla hash interna. Este fue mi primer pensamiento, así, pero previó dos problemas con esta idea:

  1. Una tabla hash no es del todo útil cuando usted no tiene una función de código hash adecuada para el tipo de que se trate.
  2. Simplemente rastrear el "recuento" de un objeto en una colección no es lo mismo que almacenar el objeto.

Como Meta-Knight sugirió, tal vez un ejemplo sería hacer esto más claro:

public class ExpensiveObject() { 
    private ExpensiveObject() { 
     // very intense operations happening in here 
    } 

    public ExpensiveObject CreateExpensiveObject() { 
     return new ExpensiveObject(); 
    } 
} 

static void Main() { 
    var expensiveObjects = new ConcurrentBag<ExpensiveObject>(); 

    for (int i = 0; i < 5; i++) { 
     expensiveObjects.Add(ExpensiveObject.CreateExpensiveObject()); 
    } 

    // after this point in the code, I want to believe I have 5 new 
    // expensive objects in my collection 

    while (expensiveObjects.Count > 0) { 
     ExpensiveObject expObj = null; 
     bool objectTaken = expensiveObjects.TryTake(out expObj); 
     if (objectTaken) { 
      // here I THINK I am queueing a particular operation to be 
      // executed on 5 separate threads for 5 separate objects, 
      // but if ConcurrentBag is a hashtable then I've just received 
      // the object 5 times and so I am working on the same object 
      // from 5 threads at the same time! 
      ThreadPool.QueueUserWorkItem(DoWorkOnExpensiveObject, expObj); 
     } else { 
      break; 
     } 
    } 
} 

static void DoWorkOnExpensiveObject(object obj) { 
    ExpensiveObject expObj = obj as ExpensiveObject; 
    if (expObj != null) { 
     // some work to be done 
    } 
} 
+0

+1, ya que es bueno saber la existencia de esta clase – Konamiman

+0

Dan-o: Su comentario de 5 líneas en su código de ejemplo no tiene sentido. Por supuesto que tiene 5 objetos independientes en la bolsa en ese punto ... el "nuevo" operador en [public ExpensiveObject CreateExpensiveObject()] lo garantiza. – Boogaloo

+0

mmm .. mi error. No he usado hashes en el pasado. Había supuesto que el generador de hash predeterminado crearía un valor de hash único por objeto, que podría anular con su propio generador de hash. No me molestes :) – Boogaloo

Respuesta

8

Si nos fijamos en los detalles de ConcurrentBag<T>, encontrará que es, a nivel interno, básicamente, modificado para requisitos particulares vinculados lista.

Dado que las bolsas pueden contener duplicados, y no son accesibles por índice, una lista doblemente unida es una muy buena opción para la implementación. Esto permite que el bloqueo tenga un grosor bastante fino para insertar y extraer (no es necesario bloquear toda la colección, solo los nodos alrededor de donde está insertando/quitando). Como no te preocupan los duplicados, no hay hash involucrado. Esto hace que una lista de doble enlace sea perfecta.

+0

Buen punto: ni siquiera pensé en el hecho de que una bolsa no necesita hacer ninguna coincidencia, solo toma objetos y los devuelve. Lo que me pareció un problema realmente confuso de repente no parece tan desconcertante. –

+0

Dado que algunos otros respondedores tienen diferentes respuestas (aunque la suya tiene más sentido para mí), me gustaría saber dónde encontraste estos detalles. Además, ese es un buen punto acerca de tener que bloquear los nodos donde la inserción/eliminación están ocurriendo ... Me gustaría saber, entonces, 'ConcurrentQueue ' en realidad, en el interior, una 'LinkedList 'también ? (De lo contrario, parece que enqueuing/dequeueing sería innecesariamente caro). –

+0

Puede usar Reflector en System.dll en .NET 4 beta 2: verá la implementación completa de este. En realidad, no es una LinkedList , sino una ConcurrentBag .ThreadLocalList interna utilizando un ConcurrentBag .Node, pero básicamente es una lista personalizada de doble enlace. –

0

Dado que el orden no importa, un ConcurrentBag podría estar usando una tabla hash detrás de las escenas para permitir la recuperación rápida de datos. Pero a diferencia de Hashset, una bolsa acepta duplicados. Tal vez cada elemento se puede emparejar con una propiedad Count que se establece en 1 cuando se agrega un elemento. Si agrega el mismo artículo por segunda vez, puede simplemente incrementar la propiedad Count de este artículo.

Luego, para eliminar un artículo que tiene un recuento mayor que uno, puede disminuir el conteo de este artículo. Si el recuento fuera uno, eliminarías el par Item-Count de la tabla hash.

+0

Parece que usted y yo teníamos ideas similares, pero considere esto: primero, esto restringiría el uso de 'ConcurrentBag ' a los tipos que son adecuados para ser utilizados como claves. En segundo lugar, si simplemente tiene una propiedad 'Count' en cada entrada en' Hashset', entonces el objeto realmente no está * en * la bolsa, lo cual siento que básicamente frustra el propósito. (Entonces, si tengo 10 copias de la misma 'Cosa' en mi 'ConcurrentBag ', y llamo 'TryTake' 10 veces, ¿qué se devuelve después de la primera vez? ¿El mismo' 'Cosa '? Entonces creo que tengo 10 objetos pero realmente tengo 1.) –

+0

Si dos elementos se consideran duplicados en el ConcurrentBag, ¿no son los mismos? Si son iguales, ¿no es de esperar que si llamas TryTake varias veces obtendrás exactamente el mismo objeto? No estoy seguro de cómo un ConcurrentBag podría funcionar bien con "tipos que no son adecuados para ser utilizados como claves" ... –

+0

Si tuviera un ejemplo concreto me ayudaría mucho a visualizar el problema que menciona ;-) –

0

Bueno, en smalltalk (de donde viene la noción de una bolsa), la colección es básicamente lo mismo que un hash, aunque uno que permite duplicados. Sin embargo, en lugar de almacenar el objeto duplicado, mantiene un "conteo de ocurrencias", por ejemplo, un recuento de cada objeto. Si ConcurrentBag es una implementación fiel, esto debería darle un punto de partida.

0

Creo que el concepto de 'Bolsa' es sinónimo de 'Multiset'.

Existen varias implementaciones "Bag"/"Multiset" (estas son java) que son de código abierto si le interesa cómo se implementan.

Estas implementaciones muestran que una 'Bolsa' se puede implementar de muchas formas dependiendo de sus necesidades. Hay ejemplos de TreeMultiset, HashMultiset, LinkedHashMultiset, ConcurrentHashMultiset.

Google Colecciones
Google tiene una serie de "MultiSet" implementations, uno de ellos un ConcurrentHashMultiset.

Apache Commons
Apache tiene una serie de implementaciones de "Bolsas".

2

Hay un poco de buena información sobre ConcurrentBag aquí: http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/03/c.net-little-wonders-concurrentbag-and-blockingcollection.aspx

La forma en que funciona el ConcurrentBag es tomar ventaja de la nueva tipo ThreadLocal (nuevo en System.Threading para .NET 4.0) para que cada hilo usando la bolsa tiene una lista local solo para ese hilo.

Esto significa que agregar o quitar a una lista local de subprocesos requiere una sincronización muy baja . El problema viene en donde un hilo va a consumir un elemento pero su lista local está vacía. En este caso , la bolsa realiza "robo de trabajo" donde roba un artículo de otro subproceso que tiene elementos en su lista. Esto requiere un nivel más alto de sincronización que agrega un poco de sobrecarga a la operación de toma.

Cuestiones relacionadas