仅锁定ID

时间:2013-05-16 12:08:18

标签: c# .net

我有一个方法需要运行exclusivley运行一段代码,但我想只在真正需要的时候添加这个限制。根据Id值(Int32),我将加载/修改不同的对象,因此锁定所有线程的访问权限没有意义。这是第一次尝试这样做 -

private static readonly ConcurrentDictionary<int, Object> LockObjects = new ConcurrentDictionary<int, Object>();
void Method(int Id)
{
    lock(LockObjects.GetOrAdd(Id,new Object())
    {
       //Do the long running task here - db fetches, changes etc
       Object Ref;
       LockObjects.TryRemove(Id,out Ref);
    }

}

我怀疑这是否有用 - TryRemove可能会失败(这将导致ConcurrentDictionary继续变大)。

一个更明显的错误是TryRemove成功删除了Object但是如果有其他线程(对于相同的Id)正在等待(锁定)此对象,然后一个具有相同Id的新线程进入并且添加一个新的Object并开始处理,因为没有其他人在等待它刚添加的Object。

我应该使用TPL或某种ConcurrentQueue来排队我的任务吗?什么是最简单的解决方案?

4 个答案:

答案 0 :(得分:3)

我使用类似的方法锁定相关项目的资源,而不是全面的资源锁定...它完美地运作。

你几乎在那里,但你真的不需要从字典中删除对象;只需让具有该id的下一个对象获得对象的锁定。

您的应用程序中唯一ID的数量是否有限制?这个限制是什么?

答案 1 :(得分:1)

我看到的主要语义问题是,对象可以被锁定而不会在集合中列出,因为锁中的最后一行会将其删除,等待的线程可以将其拾取并锁定它。

将集合更改为应该保护锁定的对象集合。 将其命名为LockedObjects并且不要从集合中删除对象,除非您不再期望需要该对象。

我总是将这种类型的对象视为,而不是锁定或阻止对象;对象没有被锁定,它是锁定代码序列的关键。

答案 2 :(得分:1)

我使用了以下方法。不检查原始ID,而是获取int类型的小型哈希码来获取现有对象以进行锁定。储物柜的数量取决于您的情况-储物柜的数量越多,发生碰撞的可能性就越小。

class ThreadLocker
{
    const int DEFAULT_LOCKERS_COUNTER = 997;
    int lockersCount;
    object[] lockers;

    public ThreadLocker(int MaxLockersCount)
    {
        if (MaxLockersCount < 1) throw new ArgumentOutOfRangeException("MaxLockersCount", MaxLockersCount, "Counter cannot be less, that 1");
        lockersCount = MaxLockersCount;
        lockers = Enumerable.Range(0, lockersCount).Select(_ => new object()).ToArray();
    }
    public ThreadLocker() : this(DEFAULT_LOCKERS_COUNTER) { }

    public object GetLocker(int ObjectID)
    {
        var idx = (ObjectID % lockersCount + lockersCount) % lockersCount;
        return lockers[idx];
    }
    public object GetLocker(string ObjectID)
    {
        var hash = ObjectID.GetHashCode();
        return GetLocker(hash);
    }
    public object GetLocker(Guid ObjectID)
    {
        var hash = ObjectID.GetHashCode();
        return GetLocker(hash);
    }
}

用法:

partial class Program
{
    static ThreadLocker locker = new ThreadLocker();
    static void Main(string[] args)
    {
        var id = 10;
        lock(locker.GetLocker(id))
        {

        }
    }
}

当然,您可以使用任何哈希码函数来获取对应的数组索引。

答案 3 :(得分:0)

如果您要使用ID本身并且不允许散列代码引起冲突,则可以采用下一种方法。维护对象的字典并存储有关要使用ID的线程数的信息:

class ThreadLockerByID<T>
{
    Dictionary<T, lockerObject<T>> lockers = new Dictionary<T, lockerObject<T>>();

    public IDisposable AcquireLock(T ID)
    {
        lockerObject<T> locker;
        lock (lockers)
        {
            if (lockers.ContainsKey(ID))
            {
                locker = lockers[ID];
            }
            else
            {
                locker = new lockerObject<T>(this, ID);
                lockers.Add(ID, locker);
            }
            locker.counter++;
        }
        Monitor.Enter(locker);
        return locker;
    }
    protected void ReleaseLock(T ID)
    {
        lock (lockers)
        {
            if (!lockers.ContainsKey(ID))
                return;

            var locker = lockers[ID];

            locker.counter--;

            if (Monitor.IsEntered(locker))
                Monitor.Exit(locker);

            if (locker.counter == 0)
                lockers.Remove(locker.id);
        }
    }

    class lockerObject<T> : IDisposable
    {
        readonly ThreadLockerByID<T> parent;
        internal readonly T id;
        internal int counter = 0;
        public lockerObject(ThreadLockerByID<T> Parent, T ID)
        {
            parent = Parent;
            id = ID;
        }
        public void Dispose()
        {
            parent.ReleaseLock(id);
        }
    }
}

用法:

partial class Program
{
    static ThreadLockerByID<int> locker = new ThreadLockerByID<int>();
    static void Main(string[] args)
    {
        var id = 10;
        using(locker.AcquireLock(id))
        {

        }
    }
}