Мелкозернистая синхронизация списка общих элементов - PullRequest
0 голосов
/ 08 ноября 2018

Я использую поточно-ориентированную стороннюю библиотеку для извлечения данных из истории.

Режим работы для типичного сценария следующий:

Library instance;

Result[] Process(string[] itemNames) {
    var itemsIds = instance.ReserveItems(itemNames);
    Result[] results = instance.ProcessItems(itemIds);
    instance.ReleaseItems(itemIds);
    return results;
}

Library - это класс, создание которого требует больших затрат, поэтому он используется здесь как синглтон (instance) и отлично работает с несколькими потоками.

Однако иногда я замечаю, что Результат помечается как неудачный («элемент не найден»), когда несколько потоков пытаются выполнить Process с массивом itemNames, который разделяет некоторые общие элементы . Поскольку библиотека очень плохо документирована, это было неожиданно.

При интенсивном ведении журнала я пришел к выводу, что поток может освободить элемент в то же время, когда другой собирается его обработать.

После пары писем поставщику библиотеки я узнал, что instance делит список зарезервированных элементов между потоками, и что необходимо синхронизировать вызовы ...

Компиляция некоторой части библиотеки подтвердила это: существует список m_items уровня класса, который используется и ReserveItems, и ReleaseItems.

Итак, я представляю следующие отходы:

Result[] Process(string[] itemNames) {
    lock(instance) {
        var itemsIds = instance.ReserveItems(itemNames);
        Result[] results = instance.ProcessItems(itemIds);
        instance.ReleaseItems(itemIds);
       return results;
    }
}

Но мне это кажется слишком жестоким.

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

РЕДАКТИРОВАТЬ - 2018-11-09

Я заметил, что все тело метода ProcessItems библиотеки заключено в оператор блокировки ...

Так что любая попытка точной синхронизации вокруг этого тщетна. Я закончил тем, что включил свое тело метода Process в оператор блокировки, снижение производительности, как и ожидалось, вообще не ощущается.

1 Ответ

0 голосов
/ 08 ноября 2018

Вы можете установить блокировку для каждого идентификатора элемента. Это может принимать форму Dictionary<string, object>, где значением является объект блокировки (new object()).

Если вы хотите обрабатывать один и тот же идентификатор элемента в нескольких потоках одновременно, не блокируя все в случае конфликта, вы можете отследить большее состояние в словаре, чтобы сделать это. В качестве примера вы можете использовать Dictionary<string, Lazy<Result>>. Первый поток, которому потребуется идентификатор элемента, будет инициализирован и непосредственно потребляет ленивый. Затем другие потоки могут обнаружить, что выполняется операция с этим идентификатором элемента, а также использовать ленивый.

...