tags:

views:

404

answers:

5

Hi,

I have a producer/consumer process. The consumed oject as 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 bit 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)

+1  A: 

Can you make your IDs to be unique for each object? If so, you could just apply a lock on the object itself.

Konamiman
The question already answers that: No.
Henk Holterman
+2  A: 

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.

Damien_The_Unbeliever
Yes, nice trick. But if it is suitable depends on a lot of factors.
Henk Holterman
Yes - for instance, if all ID values == 1 modulo 32, then it's not so nice :-)
Damien_The_Unbeliever
31 will probably work better (prime number)
Henk Holterman
-1, Come on, this only reduces the threadrace probability by a factor, does not solve it.
Pop Catalin
@Pop - I mentioned already that there would be some false sharing. If you want to completely eliminate that, then go ahead - allocate 4000000000 locks.
Damien_The_Unbeliever
@Henk - yes, that may work better if there's a skew to the distribution of ID values. If they're evenly distributed, then I'm inclined to a power of 2.
Damien_The_Unbeliever
Pop, re the question: "too many objects created while only one or two with the same ID a day". Damiens idea solves that nicely. But only Toto can say how bad some false sharing is.
Henk Holterman
" ... then go ahead - allocate 4000000000 locks", the number of allocated locks is equal to the number of worker threads that lock on distinct IDs. (that could be just a few dozen or hundred or less)
Pop Catalin
Damine, such a large number would defy the purpose. I think this scales nicely from 10 to 1000 or so with a trade off between memory and collisions.
Henk Holterman
A: 

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:

  1. Create an object, say CCustomer that would hold:

    • An object of type object
    • And a bool - for instance, bool bInProgress
  2. 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**.
KMan
Could you at least add a small summary, just in case the link goes dead? And that seems solid but not really faster than what Toto is doing now.
Henk Holterman
Thanks for pointing that out, Henk!
KMan
A: 

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.

weismat
This does not address the problem: There could be different objects with the same ID. Your queue would not prevent 2 of those to be consumed at the same time.
Henk Holterman
you are totaly right henk
Toto
A: 

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).

Marek
I do'nt have the lead on the item's class
Toto