Как может быть реализован класс, например .NET ConcurrentBag <T>?

Я очень заинтригован существованием класса ConcurrentBag<T> в следующей платформе .NET 4.0:

Сумки полезны для хранения объектов, когда упорядочение не имеет значения, и в отличие от наборов, сумки поддерживают дубликаты.

Мой вопрос: как можно реализовать эту идею? Большинство коллекций, с которыми я знаком, в основном составляют (под капотом) некоторую форму массива, в каком порядке не может быть "материя", но есть порядок (именно поэтому, даже если это не нужно, перечисление будет почти всегда проходят неизменную коллекцию, будь то List, Queue, Stack и т.д. в той же последовательности).

Если бы я должен был догадаться, я мог бы предположить, что внутри он мог бы быть Dictionary<T, LinkedList<T>>; но это на самом деле кажется довольно сомнительным, поскольку не имеет смысла использовать в качестве ключа любой тип T.

То, что я ожидаю/надеюсь, это то, что на самом деле это уже установленный тип объекта, который уже "разобрался", и что кто-то, кто знает об этом установленном типе, может рассказать мне об этом. Это так необычно для меня - одна из тех концепций, которые легко понять в реальной жизни, но трудно перевести на полезный класс в качестве разработчика - вот почему я интересуюсь возможностями.

ИЗМЕНИТЬ

Некоторые респонденты предположили, что a Bag может быть формой хэш-таблицы внутри. Это была моя первоначальная мысль, но я предвидел две проблемы с этой идеей:

  • Хэш-таблица не так полезна, когда у вас нет подходящей функции хэш-кода для рассматриваемого типа.
  • Простое отслеживание объекта "счет" в коллекции не совпадает с хранением объекта.

Как предложил Meta-Knight, возможно, пример сделает это более понятным:

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
    }
}

Ответ 1

Если вы посмотрите на детали ConcurrentBag<T>, вы обнаружите, что это внутри, в основном настраиваемый связанный список.

Так как Bags могут содержать дубликаты и недоступны по индексу, двунаправленный список является очень хорошим вариантом для реализации. Это позволяет блокировать довольно мелкозернистую область для вставки и удаления (вам не нужно блокировать всю коллекцию, только узлы, где вы вставляете/удаляете). Поскольку вы не беспокоитесь о дубликатах, это не связано с хешированием. Это делает двойной список ссылок идеальным.

Ответ 2

Здесь есть хорошая информация о ConcurrentBag: http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/03/c.net-little-wonders-concurrentbag-and-blockingcollection.aspx

Как работает ConcurrentBag заключается в том, чтобы воспользоваться новым Тип ThreadLocal (новый в System.Threading для .NET 4.0), чтобы каждая нить, использующая сумку, имеет список локально только для этой темы.

Это означает, что добавление или удаление для локального списка потоков требуется очень низкая синхронизации. Проблема возникает где поток идет на потребление элемента но его локальный список пуст. В этом если сумка выполняет "кражу работы", где он будет грабить предмет из другого который содержит элементы в своем списке. Для этого требуется более высокий уровень синхронизация, которая добавляет немного накладные расходы на операцию.

Ответ 3

Поскольку упорядочение не имеет значения, ConcurrentBag может использовать хеш-таблицу за кулисами, чтобы обеспечить быстрый поиск данных. Но в отличие от Hashset сумка принимает дубликаты. Возможно, каждый элемент может быть сопряжен с свойством Count, которое устанавливается в 1 при добавлении элемента. Если вы добавите один и тот же элемент во второй раз, вы можете просто увеличить свойство Count этого элемента.

Затем, чтобы удалить элемент с числом больше одного, вы можете просто уменьшить количество для этого элемента. Если счет был один, вы удалили бы пару Item-Count из хеш-таблицы.

Ответ 4

Ну, в smalltalk (откуда взялось понятие Bag), коллекция в основном такая же, как хэш, хотя и позволяет дублировать. Вместо того, чтобы хранить дублированный объект, он поддерживает "количество ошибок", например, пересчет каждого объекта. Если ConcurrentBag является верной реализацией, это должно дать вам отправную точку.

Ответ 5

Я считаю, что понятие "сумка" является синонимом "Multiset".

Существует несколько реализаций "Bag" / "Multiset" (они кажутся java), которые являются с открытым исходным кодом, если вас интересует, как они реализованы.

Эти реализации показывают, что "Сумка" может быть реализована любым количеством способов в зависимости от ваших потребностей. Существуют примеры TreeMultiset, HashMultiset, LinkedHashMultiset, ConcurrentHashMultiset.

Коллекции Google
В Google есть ряд "MultiSet" реализаций, один из которых является ConcurrentHashMultiset.

Apache Commons
Apache имеет ряд реализаций "Bag".