Блокировка в заводском методе - PullRequest
0 голосов
/ 15 февраля 2012

Я взаимодействую с серверной системой, где у меня никогда не должно быть более одного открытого соединения с данным объектом (идентифицируемый его числовым идентификатором), но разные потребители могут открывать и закрывать их независимо друг от друга.

Грубо говоря, у меня есть фрагмент фабричного класса, подобный этому:

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 работает очень медленно.Блокировка здесь довольно наивна и может быть очень медленной, когда существует много разных одновременных .Open вызовов, даже если они (скорее всего) ссылаются на разные идентификаторы и не перекрываются, особенно если их нет в _openItemsкеш.

Как структурировать блокировку так, чтобы я только предотвращал одновременный доступ к определенному идентификатору , а не ко всем вызывающим абонентам?

Ответы [ 2 ]

3 голосов
/ 15 февраля 2012

То, на что вы можете обратить внимание, - это полосатая блокировка. Идея состоит в том, что вы совместно используете N блокировок для M элементов (возможные идентификаторы в вашем случае) и выбираете блокировку так, чтобы для любого идентификатора выбранная блокировка всегда была одинаковой. Классический способ выбора блокировок для этого метода - деление по модулю - просто разделите 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 голос
/ 15 февраля 2012

Если вы находитесь на .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);
                }
            }
        }
    }
}

Если кто-то может увидеть какие-либо явные проблемы с этим, пожалуйста, дайте мнезнаю!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...