锁定工厂方法

本文关键字:方法 工厂 锁定 | 更新日期: 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);
                }
            }
        }
    }
}

如果有人能看到任何明显的问题,请告诉我!