soundie
@soundie

Как лучше организовать многопоточное добавление данных в словарь (Dictionary)?

Есть обычный словарь (ключ-значение):
Dictionary<int, string> dict

В словарь с частотой в несколько миллисекунд добавляются строки из трёх-пяти потоков (Threads) простым методом присваивания:
dict[i] = "какая-то строка";

Иногда ключ может совпадать, то есть будет не добавление новой строки, а обновление существующей. В интернете на stackoverflow пишут что вышеуказанная строчка потокобезопасна, поэтому проблем не будет (и действительно я вижу что ошибки в программе не вылетают). Но так как данных много и скорость их обновления высока, то проверить достоверно целостность очень трудно - мало ли, может что и пропускается.

Стали терзать сомнения, открыл статью Албахари про параллелизм, и он в данном случае рекомендует использовать тип ConcurrentDictionary для этого вместо обычного Dictionary (но оговаривает что сие будет медленнее - особенно когда периодически вызывается dict.Count), либо использовать вот такую конструкцию добавления/обновления (говорит что она быстрее в 3 раза):
lock (dict)
{
    dict[i] = "что-то";
}

А в третьем источнике Эндрю Лок рекомендует использовать Interlocked.Exchange.

Чей метод будет лучше в плане обеспечения наилучшей целостности данных и скорости для поставленной задачи? Или может для таких случаев подойдут in-memory базы данных (вроде как они лучше всего ориентированы на такие многопоточные добавления/обновления)?
  • Вопрос задан
  • 146 просмотров
Решения вопроса 2
mayton2019
@mayton2019
Bigdata Engineer
Несколько мыслей.

1. In-memory базы данных будут медленнее чем C# структуры данных. Им ведь надо перекидывать данные через границу процесса. Память не шарится соотв - сериализация-сетевые сокеты пускай даже локальные. Все это будет медленнее.

2. Я не знаю ни одной структуры данных которая-бы себя хорошо вела при конкуретнтной вставке. Concurrent - коллекции нужно только в том случае когда результат вставки в ту-же милисекунду нужен вам на чтение как результат из другого потока. Это очень строгое требование и реально очень мало систем им обладают. Я-бы предложил следующее. Если 5 потоков пишут независимо без обратной связи - то пускай пишут в буферы в несколько килобайт. И пускай 6 поток периодически собирает эти буферы. Так можно уменьшить конкуренцию. Лаг можно регулировать. Сколько хотим? 10 мс? 100мс?

Еще вариант - потоки пишут каждый в свой Dictionary. И периодически происходит merge. Пакетом. Тут надо померять performance. Вообще в конкурирующих работах с key-value ищут возможность сделать partitioning. Если удасться то будут конкурировать максимум 2 потока а не 5.
Ответ написан
Комментировать
vabka
@vabka Куратор тега C#
Токсичный шарпист

многопоточное добавление данных в словарь

Dictionary не потокобезопасен, по тому при обращении к нему из нескольких потоков нужно синхронизировать потоки (желательно при любых обращениях)

Лучше использовать ConcurrentDictionary - в нём уже решены потенциальные проблемы при конкурентном доступе (например попытке изменить запись, которая уже удалена в другом потоке или удаление удалённой, или создании уже существующей, или чтении несуществующей/удалённой)

Interlocked exchange лучше смотреть по месту, может подойдёт, а может нет.

На счёт инмемори базы тоже следует посмотреть на конкретную задачу.

UPD: ещё как хорошее решение звучит использование System.Threading.Channels, раз задача стоит накапливать записи и батчем их отправлять в СУБД.
Так мы гарантированно решаем проблему гонок, так как фактически менять данные будет только один поток
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы