锁定工厂方法
本文关键字:方法 工厂 锁定 | 更新日期: 2023-09-27 18:24:39
我正在与后端系统对接,在后端系统中,我决不能与给定对象(由其数字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的并发访问,而不是对所有呼叫者的并发访问?
您可能想要研究的是条带锁定策略。这个想法是,你为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();
}
如果您在.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);
}
}
}
}
}
如果有人能看到任何明显的问题,请告诉我!