Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I synchronize a block of code based on a key?

I have a service exposed via WCF MSMQ transport.

Part of the work this service does is it looks up an item based on a key (source, item_id). If it finds one it retrieves the database identifier and uses that to update the record. If it does not find one it inserts the new record.

I noticed it is possible for two items to come in at the same time, both see that an item does not exist in the database, and they both try to insert, but one fails with a constraint error.

I'd like to limit access to the database lookup and following code based on a key (source, item_id) so that only one thread can do the work at a time for that specific key.

I've put together some code to make this happen, but I'd like to get some feedback on if this works or if there is a better approach.

Code to use LockManager class:

public class ItemService
{
   private static LockManager lockManager = new LockManager();

   public void AddItem(Item item){
      var itemKey = item.Source + ":" + item.ItemId;
      lockManager.Work(itemKey, delegate(){ do stuff });
   }
}

LockManager class:

public class LockManager
{

    private readonly IDictionary<string, LockObject> _lockTable = 
        new Dictionary<string, LockObject>();

    public void Work(string key, Action work)
    {
        var lockObject = BorrowLockObject(key);
        try
        {
            lock (lockObject)
            {
                work();
            }
        }
        finally
        {
            ReturnLockObject(lockObject);
        }
    }

    private LockObject BorrowLockObject(string key)
    {
        lock (_lockTable)
        {
            LockObject lockObject = null;
            if (_lockTable.ContainsKey(key))
            {
                lockObject = _lockTable[key];
            }
            else
            {
                lockObject = new LockObject(key);
                _lockTable[key] = lockObject;
            }
            lockObject.Open();
            return lockObject;
        }
    }

    private void ReturnLockObject(LockObject lockObject)
    {
        lock (_lockTable)
        {
            if (lockObject.Close())
            {
                _lockTable.Remove(lockObject.GetKey());
            }
        }
    }
}

LockObject class:

public class LockObject
{
    private readonly string _key;
    private int _count;

    public LockObject(string key)
    {
        _key = key;
        _count = 0;
    }

    public string GetKey()
    {
        return _key;
    }
    public void Open()
    {
        lock(this)
        {
            _count++;    
        }    
    }

    /// <summary>
    /// Closes this lock object.
    /// </summary>
    /// <returns>True if this Lock Object is no longer in use.</returns>
    public bool Close()
    {
        lock(this)
        {
            _count--;
            return _count == 0;
        }
    }
}
like image 837
ScArcher2 Avatar asked May 26 '11 18:05

ScArcher2


People also ask

Is it possible to use the synchronized keyword for a block of code?

A Java synchronized block marks a method or a block of code as synchronized. A synchronized block in Java can only be executed a single thread at a time (depending on how you use it). Java synchronized blocks can thus be used to avoid race conditions.

Can a block be synchronized?

A Synchronized block is a piece of code that can be used to perform synchronization on any specific resource of the method. A Synchronized block is used to lock an object for any shared resource and the scope of a synchronized block is smaller than the synchronized method.

How can you synchronize a block of code in constructor?

Note that constructors cannot be synchronized — using the synchronized keyword with a constructor is a syntax error. Synchronizing constructors doesn't make sense, because only the thread that creates an object should have access to it while it is being constructed.


1 Answers

A synchronized collection of Pair<Key,Action> and a WorkManager on a separate thread working on that as a queue (back to front) would simplify this greatly. You could pop out and discard all Pairs that contain the same key after popping one to work on and finishing work on it (locking the collection when doing this).

>

  • client adds
    • lock collection
    • add
    • unlock collection

>

  • backthread iteration:
    • lock collection
    • get work item (last in collection)
    • remove from collection
    • unlock collection
    • work ... (in the meanwhile clients add more, and maybe duplicates)
    • lock collection
    • remove all items with same key (and handle properly)
    • unlock collection

BTW: public delegate void Worker(); has a shortcut in Action.

like image 192
Marino Šimić Avatar answered Sep 18 '22 14:09

Marino Šimić