我正在与后端系统连接,在后端系统中,我必须永远不会有多个与给定对象的开放连接(由它的数字ID标识),但不同的消费者可能会彼此独立地打开和关闭它们。
粗略地说,我有一个像这样的工厂类片段:
private Dictionary<ulong, IFoo> _openItems = new Dictionary<ulong, IFoo>();
private object _locker = new object();
public IFoo Open(ulong id)
{
lock (_locker)
{
if (!_openItems.ContainsKey(id))
{
_openItems[id] = _nativeResource.Open(id);
}
_openItems[id].RefCount++;
return _openItems[id];
}
}
public void Close(ulong id)
{
lock (_locker)
{
if (_openItems.ContainsKey(id))
{
_openItems[id].RefCount--;
if (_openItems[id].RefCount == 0)
{
_nativeResource.Close(id);
_openItems.Remove(id);
}
}
}
}
现在,这是问题所在。就我而言,_nativeResource.Open 非常慢。这里的锁定是相当天真的,并且当存在许多不同的并发。打开调用时可能非常慢,即使它们(很可能)指的是不同的ID并且不重叠,特别是如果它们不在_openItems中高速缓存中。
如何构建锁定,以便我只阻止并发访问到特定ID 而不是所有来电者?
答案 0 :(得分:3)
您可能希望了解的是条带锁定策略。这个想法是你为M个项目共享N个锁(在你的情况下可能是ID),并选择一个锁,这样对于任何ID,所选择的锁总是相同的。为这种技术选择锁的经典方法是模除法 - 简单地将M除以N,取余数,并使用该索引的锁:
// Assuming the allLocks class member is defined as follows:
private static AutoResetEvent[] allLocks = new AutoResetEvent[10];
// And initialized thus (in a static constructor):
for (int i = 0; i < 10; i++) {
allLocks[i] = new AutoResetEvent(true);
}
// Your method becomes
var lockIndex = id % allLocks.Length;
var lockToUse = allLocks[lockIndex];
// Wait for the lock to become free
lockToUse.WaitOne();
try {
// At this point we have taken the lock
// Do the work
} finally {
lockToUse.Set();
}
答案 1 :(得分:1)
如果您使用的是.net 4,则可以尝试ConcurrentDictionary这些内容:
private ConcurrentDictionary<ulong, IFoo> openItems = new ConcurrentDictionary<ulong, IFoo>();
private object locker = new object();
public IFoo Open(ulong id)
{
var foo = this.openItems.GetOrAdd(id, x => nativeResource.Open(x));
lock (this.locker)
{
foo.RefCount++;
}
return foo;
}
public void Close(ulong id)
{
IFoo foo = null;
if (this.openItems.TryGetValue(id, out foo))
{
lock (this.locker)
{
foo.RefCount--;
if (foo.RefCount == 0)
{
if (this.openItems.TryRemove(id, out foo))
{
this.nativeResource.Close(id);
}
}
}
}
}
如果有人能看到任何明显的问题,请告诉我!