我有一个方法需要运行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来排队我的任务吗?什么是最简单的解决方案?
答案 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))
{
}
}
}