Я использую потокобезопасную стороннюю библиотеку для извлечения данных от историка.
Режим работы для типичного сценария следующий:
Library instance;
Result[] Process(string[] itemNames) {
var itemsIds = instance.ReserveItems(itemNames);
Result[] results = instance.ProcessItems(itemIds);
instance.ReleaseItems(itemIds);
return results;
}
Library
- это класс, который является дорогостоящим для создания экземпляра, поэтому он используется здесь как singleton ( instance
), и он отлично работает против нескольких потоков.
Тем не менее, иногда я замечаю, что результат помечается как сбой («элемент не найден»), когда несколько потоков пытаются выполнить Process
с массивом itemNames, который разделяет некоторые общие элементы . Поскольку библиотека очень плохо документирована, это было неожиданным.
Благодаря интенсивному протоколированию я пришел к выводу, что поток может освобождать элемент, в то время как другой обрабатывает его.
После нескольких писем к поставщику библиотеки я узнал, что instance
разделяет список зарезервированных элементов между потоком и что необходимо синхронизировать вызовы ...
Uncompiling часть библиотеки подтвердила это: список классов 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;
}
}
Но это кажется мне слишком жестоким.
Поскольку эта библиотека отлично работает, когда разные элементы обрабатываются несколькими потоками, как выполнить более мелкозернистую синхронизацию и избежать штрафа за производительность?
EDIT - 2018-11-09
Я заметил, что весь
ProcessItems
методовProcessItems
библиотеки заключен в оператор блокировки ...Поэтому любая попытка тонкой синхронизации вокруг этого бесполезна. В итоге я
Process
тело методаProcess
в оператор блокировки, а рейтинг производительности - ожидаемый сейчас - не воспринимается вообще.
Всего 1 ответ
Вы можете реализовать блокировку для каждого элемента. Это может быть в виде Dictionary<string, object>
где значением является объект блокировки ( new object()
).
Если вы хотите обрабатывать один и тот же идентификатор элемента в нескольких потоках одновременно, не блокируя все в случае конфликта, вы можете отслеживать больше состояния в значении словаря для этого. Например, вы можете использовать Dictionary<string, Lazy<Result>>
. Первый поток, требующий идентификатор элемента, будет инициализироваться и потреблять ленивый. Другие потоки могут затем обнаружить, что выполняется операция над этим идентификатором элемента, а также потребляет ленивый.