Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to lock on an integer in C#?

Tags:

c#

.net

locking

Is there any way to lock on an integer in C#? Integers can not be used with lock because they are boxed (and lock only locks on references).

The scenario is as follows: I have a forum based website with a moderation feature. What I want to do is make sure that no more than one moderator can moderate a post at any given time. To achieve this, I want to lock on the ID of the post.

I've had a couple of ideas so far (e.g. using a dictionary<int, object>), but I'm looking for a better and cleaner way.

Any suggestions?

like image 390
Waleed Eissa Avatar asked Apr 23 '09 10:04

Waleed Eissa


4 Answers

I like doing it like this

public class Synchronizer {
    private Dictionary<int, object> locks;
    private object myLock;

    public Synchronizer() {
        locks = new Dictionary<int, object>();
        myLock = new object();
    }

    public object this[int index] {
        get {
            lock (myLock) {
                object result;
                if (locks.TryGetValue(index, out result))
                    return result;

                result = new object();
                locks[index] = result;
                return result;
            }
        }
    }
}

Then, to lock on an int you simply (using the same synchronizer every time)

lock (sync[15]) { ... }

This class returns the same lock object when given the same index twice. When a new index comes, it create an object, returning it, and stores it in the dictionary for next times.

It can easily be changed to work generically with any struct or value type, or to be static so that the synchronizer object does not have to be passed around.

like image 97
configurator Avatar answered Oct 15 '22 22:10

configurator


If it's a website then using an in-process lock probably isn't the best approach as if you need to scale the site out onto multiple servers, or add another site hosting an API (or anything else that would require another process accessing the same data to exist) then all your locking strategies are immediately ineffective.

I'd be inclined to look into database-based locking for this. The simplest approach is to use optimistic locking with something like a timestamp of when the post was last updated, and to reject updates made to a post unless the timestamps match.

like image 35
Greg Beech Avatar answered Oct 16 '22 00:10

Greg Beech


I've read a lot of comments mentioning that locking isn't safe for web applications, but, other than web farms, I haven't seen any explanations of why. I would be interested in hearing the arguments against it.

I have a similar need, though I'm caching re-sized images on the hard drive (which is obviously a local action so a web farm scenario isn't an issue).

Here is a redone version of what @Configurator posted. It includes a couple features that @Configurator didn't include:

  1. Unlocking: Ensures the list doesn't grow unreasonably large (we have millions of photos and we can have many different sizes for each).
  2. Generic: Allows locking based on different data types (such as int or string).

Here's the code...

/// <summary>
/// Provides a way to lock a resource based on a value (such as an ID or path).
/// </summary>
public class Synchronizer<T>
{

    private Dictionary<T, SyncLock> mLocks = new Dictionary<T, SyncLock>();
    private object mLock = new object();

    /// <summary>
    /// Returns an object that can be used in a lock statement. Ex: lock(MySync.Lock(MyValue)) { ... }
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public SyncLock Lock(T value)
    {
        lock (mLock)
        {
            SyncLock theLock;
            if (mLocks.TryGetValue(value, out theLock))
                return theLock;

            theLock = new SyncLock(value, this);
            mLocks.Add(value, theLock);
            return theLock;
        }
    }

    /// <summary>
    /// Unlocks the object. Called from Lock.Dispose.
    /// </summary>
    /// <param name="theLock"></param>
    public void Unlock(SyncLock theLock)
    {
        mLocks.Remove(theLock.Value);
    }

    /// <summary>
    /// Represents a lock for the Synchronizer class.
    /// </summary>
    public class SyncLock
        : IDisposable
    {

        /// <summary>
        /// This class should only be instantiated from the Synchronizer class.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="sync"></param>
        internal SyncLock(T value, Synchronizer<T> sync)
        {
            Value = value;
            Sync = sync;
        }

        /// <summary>
        /// Makes sure the lock is removed.
        /// </summary>
        public void Dispose()
        {
            Sync.Unlock(this);
        }

        /// <summary>
        /// Gets the value that this lock is based on.
        /// </summary>
        public T Value { get; private set; }

        /// <summary>
        /// Gets the synchronizer this lock was created from.
        /// </summary>
        private Synchronizer<T> Sync { get; set; }

    }

}

Here's how you can use it...

public static readonly Synchronizer<int> sPostSync = new Synchronizer<int>();
....
using(var theLock = sPostSync.Lock(myID))
lock (theLock)
{
    ...
}
like image 39
Brian Avatar answered Oct 15 '22 22:10

Brian


This option builds on the good answer provided by configurator with the following modifications:

  1. Prevents the size of the dictionary from growing uncontrollably. Since, new posts will get new ids, your dictionary of locks will grow indefinitely. The solution is to mod the id against a maximum dictionary size. This does mean that some ids will have the same lock (and have to wait when they would otherwise not have to), but this will be acceptable for some dictionary size.
  2. Uses ConcurrentDictionary so there is no need for a separate dictionary lock.

The code:

internal class IdLock
{
    internal int LockDictionarySize
    {
        get { return m_lockDictionarySize; }
    }
    const int m_lockDictionarySize = 1000;
    ConcurrentDictionary<int, object> m_locks = new ConcurrentDictionary<int, object>();
    internal object this[ int id ]
    {
        get
        {
            object lockObject = new object();
            int mapValue = id % m_lockDictionarySize;
            lockObject = m_locks.GetOrAdd( mapValue, lockObject );
            return lockObject;
        }
    }
}

Also, just for completeness, there is the alternative of string interning: -

  1. Mod the id against the maximum number of interned id strings you will allow.
  2. Convert this modded value to a string.
  3. Concatenate the modded string with a GUID or namespace name for name collision safety.
  4. Intern this string.
  5. lock on the interned string. See this answer for some information:

The only benefit of the string interning approach is that you don't need to manage a dictionary. I prefer the dictionary of locks approach as the intern approach makes a lot of assumptions about how string interning works and that it will continue to work in this way. It also uses interning for something it was never meant / designed to do.

like image 34
acarlon Avatar answered Oct 15 '22 22:10

acarlon