5

我有一个需要运行独占运行代码块的方法,但我只想在确实需要时添加此限制。根据 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 的新线程进来并添加一个新的对象并开始处理,因为没有其他人在等待它刚刚添加的对象。

我应该使用 TPL 还是某种 ConcurrentQueue 来代替我的任务?最简单的解决方案是什么?

4

4 回答 4

3

我使用类似的方法来锁定相关项目的资源,而不是一揽子资源锁定......它工作得很好。

你快到了,但你真的不需要从字典中删除对象;只需让具有该 id 的下一个对象获得该对象的锁定。

你的应用程序中唯一 ID 的数量肯定是有限制的吗?那限制是多少?

于 2013-05-16T12:18:29.543 回答
1

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

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

我一直认为这种类型的对象是一把钥匙,而不是锁或阻塞的对象;该对象未锁定,它是锁定代码序列的关键。

于 2013-05-16T12:27:03.353 回答
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))
        {

        }
    }
}

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

于 2018-08-10T07:24:14.760 回答
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))
        {

        }
    }
}
于 2018-08-10T07:27:37.163 回答