Четкая синхронизация общего списка элементов

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

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

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>> . Первый поток, требующий идентификатор элемента, будет инициализироваться и потреблять ленивый. Другие потоки могут затем обнаружить, что выполняется операция над этим идентификатором элемента, а также потребляет ленивый.


Есть идеи?

10000