Do pobierania danych z historyka używam biblioteki innej firmy z bezpieczną wątkowo.

Tryb pracy dla typowego scenariusza jest następujący:

Library instance;

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

Library jest klasą, która jest kosztowna w tworzeniu instancji, więc jest tutaj używana jako singleton (instance) i działa doskonale z wieloma wątkami.

Jednak czasami zauważam, że Wynik jest oznaczony jako nieudany („nie znaleziono elementu”), gdy wiele wątków próbuje wykonać Process z tablicą itemNames, która współdzieli niektóre wspólne elementy. Ponieważ biblioteka jest bardzo źle udokumentowana, było to nieoczekiwane.

Intensywnie logując się wydedukowałem, że wątek może wypuścić element w tym samym czasie, gdy inny ma zamiar go przetworzyć.

Po kilku mailach do dostawcy biblioteki dowiedziałem się, że instance udostępnia listę zarezerwowanych pozycji między wątkami i że konieczna jest synchronizacja połączeń...

Dekompilacja części biblioteki potwierdziła to: istnieje lista m_items na poziomie klasy, która jest używana zarówno przez ReserveItems, jak i ReleaseItems.

Wyobrażam sobie więc następujące marnotrawstwo:

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

Ale wydaje mi się to zbyt gwałtowne.

Ponieważ ta biblioteka działa doskonale, gdy różne elementy są przetwarzane przez wiele wątków, w jaki sposób można przeprowadzić bardziej szczegółową synchronizację i uniknąć spadku wydajności?

EDYTUJ - 2018-11-09

Zauważyłem, że całe ciało metody ProcessItems Biblioteki jest zawarte w instrukcji lock...

Więc jakakolwiek próba dokładnej synchronizacji wokół tego jest daremna. Skończyło się na tym, że zamknąłem treść mojej metody Process w instrukcji lock, spadek wydajności jest - zgodnie z oczekiwaniami - w ogóle niezauważalny.

1
Larry 8 listopad 2018, 11:34

1 odpowiedź

Najlepsza odpowiedź

Możesz zaimplementować blokadę według identyfikatora produktu. Może to przybrać formę Dictionary<string, object>, gdzie wartością jest obiekt blokady (new object()).

Jeśli chcesz przetwarzać ten sam identyfikator elementu w wielu wątkach jednocześnie bez blokowania wszystkiego w przypadku konfliktu, możesz w tym celu śledzić więcej stanu w wartości słownika. Jako przykład możesz użyć Dictionary<string, Lazy<Result>>. Pierwszy wątek, który potrzebuje identyfikatora elementu, zainicjuje się i bezpośrednio zużyje leniwy. Inne wątki mogą następnie wykryć, że operacja jest w toku na tym identyfikatorze elementu, a także zużywać leniwe.

1
usr 8 listopad 2018, 17:03