Reputation: 18041
I am using a thread-safe third party library to retrieve data from a historian.
The operating mode for a typical scenario is the following:
Library instance;
Result[] Process(string[] itemNames) {
var itemsIds = instance.ReserveItems(itemNames);
Result[] results = instance.ProcessItems(itemIds);
instance.ReleaseItems(itemIds);
return results;
}
Library
is a class that is expensive to instantiate, so it is used here as a singleton (instance
), and it works perfectly against multiple threads.
However, I notice sometimes that a Result is marked as failed ("item not found"), when multiple threads attempt to execute Process
with an itemNames array that shares some common items. Because the library is very badly documented, that was unexpected.
By intensively logging, I have deduced that a thread could release an item at the same time another one is about to process it.
After a couple of mails to the library's vendor, I learnt that instance
shares a list of reserved items between thread, and that it is necessary to synchronize the calls...
Uncompiling some part of the library confirmed this: there is a class level m_items list that is used by both ReserveItems and ReleaseItems.
So I envision the following waste:
Result[] Process(string[] itemNames) {
lock(instance) {
var itemsIds = instance.ReserveItems(itemNames);
Result[] results = instance.ProcessItems(itemIds);
instance.ReleaseItems(itemIds);
return results;
}
}
But it seems a bit too violent to me.
As this Library works perfectly when different items are processed by multiple thread, how can perform a more fine-grained synchronization and avoid a performance penalty?
EDIT - 2018-11-09
I noticed that the whole
ProcessItems
method body of the Library is enclosed into a lock statement...So any attempt at fine synchronization around this is futile. I ended up enclosing my
Process
method body in a lock statement as well, the performance penalty is -as expected now- not perceptible at all.
Upvotes: 1
Views: 140
Reputation: 171216
You could implement a lock per item ID. That could take the form of a Dictionary<string, object>
where the value is the lock object (new object()
).
If you want to process the same item ID on multiple threads at the same time without blocking everything in case of conflict, you could track more state in the dictionary value to do that. As an example, you could use a Dictionary<string, Lazy<Result>>
. The first thread to need an item ID would initialize and directly consume the lazy. Other threads can then detect that an operation is in progress on that item ID and also consume the lazy.
Upvotes: 1