Concurrent object locks based on ID field
I have a producer/consumer process. The consumed object has an ID property (of type integer), I want only one object with the same ID to be consumed at a time. How can I perform this ?
Maybe I can do something like this, but I don't like it (too many objects created while only one or two with the same ID a day can be consumed and the lock(_lockers) is a b开发者_如何学Goit time consuming :
private readonly Dictionary<int,object> _lockers = new Dictionary<int,object>();
private object GetLocker(int id)
{
lock(_lockers)
{
if(!_lockers.ContainsKey(id))
_lockers.Add(id,new object());
return _lockers[id];
}
}
private void Consume(T notif)
{
lock(GetLocker(notif.ID))
{
...
}
}
enter code here
NB : Same question with the ID property being of type string (in that cas maybe I can lock over the string.Internal(currentObject.ID)
As indicated in comment, one approach would be to have a fixed pool of locks (say 32), and take the ID modulo 32 to determine which lock to take. This would result in some false sharing of locks. 32 is number picked from the air - it would depend on your distibution of ID values, how many consumers, etc.
Can you make your IDs to be unique for each object? If so, you could just apply a lock on the object itself.
First off,
have you profiled to establish that lock(_lockers)
is indeed a bottleneck? Because if it's not broken, don't fix it.
Edit: I didn't read carefully enough, this is about the (large) number of helper objects created.
I think Damien's got a good idea for that, I'll leave this bit about the strings:
Regarding
NB : Same question with the ID property being of type string (in that cas maybe I can lock over the string.Internal(currentObject.ID)
No, bad idea. You can lock on a string but then you will have to worry about wheter they may be interned. Hard to be sure they are unique.
I would consider a synced FIFO queue as a seperate class/singleton for all your produced objects - the producers enqueues the objects and the consumers dequeue - thus the actual objects do not require any synchronization anymore. The synchronisation is then done outside the actual objects.
How about assigning IDs from a pool of ID objects and locking on these?
When you create your item:
var item = CreateItem();
ID id = IDPool.Instance.Get(id);
//assign id to object
item.ID = id;
the ID pool creates and maintains shared ID instances:
class IDPool
{
private Dictionary<int, ID> ids = new Dictionary<int, ID>();
public ID Get(int id)
{
//get ID from the shared pool or create new instance in the pool.
//always returns same ID instance for given integer
}
}
you then lock on ID which is now a reference in your Consume method:
private void Consume(T notif)
{
lock(notif.ID)
{
...
}
}
This is not the optimal solution and only offsets the problem to a different place - but if you believe that you have pressure on the lock you may get a performance improvement using this approach (given that e.g. you objects are created on a single thread, you do not need to synchronize the ID pool then).
See How to: Synchronize a Producer and a Consumer Thread (C# Programming Guide)
In addition to simply preventing simultaneous access with the lock keyword, further synchronization is provided by two event objects. One is used to signal the worker threads to terminate, and the other is used by the producer thread to signal to the consumer thread when a new item has been added to the queue. These two event objects are encapsulated in a class called SyncEvents. This allows the events to be passed to the objects that represent the consumer and producer threads easily.
--Edit--
A simple code snippet that I wrote sometime back; see if this helps. I think this is what weismat is pointing towards?
--Edit--
How about following:
Create an object, say CCustomer that would hold:
- An object of type object
- And a bool - for instance, bool bInProgress
Dictionary that would hold
now when you check following
if(!_lockers.ContainsKey(id))
_lockers.Add(id,new CCustomer(/**bInProgress=true**/));
return _lockers[id]; **//here you can check the bInProgress value and respond accordingly**.
精彩评论