8

ConcurrentDictionary Pitfall - GetOrAdd 和 AddOrUpdate 的委托工厂是否同步?注意 AddOrUpdate 不是原子的(并且不能保证委托不会运行超过一次)。

我正在尝试使用并发字典 a la here来实现名称锁定实现,但是字典不应该永远增长,如下所示:

public class ConcurrentDictionaryNamedLocker : INamedLocker
{
    // the IntObject values serve as the locks and the counter for how many RunWithLock jobs 
    // are about to enter or have entered the critical section.
    private readonly ConcurrentDictionary<string, IntObject> _lockDict = new ConcurrentDictionary<string, IntObject>();
    private static readonly IntObject One = new IntObject(1);
    private readonly Func<string, IntObject, IntObject> _decrementFunc = (s, o) => o - 1;
    private readonly Func<string, IntObject, IntObject> _incrementFunc = (s, o) => o + 1;
    private readonly Func<string, IntObject> _oneFunc = s => new IntObject(1);
    private readonly Func<string, IntObject> _zeroFunc = s => new IntObject(0);

    public TResult RunWithLock<TResult>(string name, Func<TResult> body)
    {
        name = name.ToLower();
        TResult toReturn;
        lock (_lockDict.AddOrUpdate(name, _oneFunc, _incrementFunc))
        {
            toReturn = body();
            if (!_lockDict.TryRemove(name, One))
                _lockDict.AddOrUpdate(name, _zeroFunc, _decrementFunc);
        }
        return toReturn;
    }

    public void RunWithLock(string name, Action body)
    {
        name = name.ToLower();
        lock (_lockDict.AddOrUpdate(name, _oneFunc, _incrementFunc))
        {
            body();
            if (!_lockDict.TryRemove(name, One))
                _lockDict.AddOrUpdate(name, _zeroFunc, _decrementFunc);
        }
    }
}

但问题是 AddOrUpdate 不是原子的,所以我看到当存在争用时条目通常不会被删除。我相当肯定,如果 AddOrUpdate 是原子的,那么上面的代码将完成它的工作,并且条目将被适当地删除。

注意这里提到的通过 key+val 扩展方法 TryRemove(key,val) 使用条件删除。此外,IntObject 是一个简单的 int 可变对象包装器。

我有哪些选择?是否有任何并发​​字典实现具有 1. 原子条件(键和值)删除和 2. AddOrUpdate 是原子的并确保委托不会多次运行?

还有其他想法吗?我希望命名的储物柜速度快,但在给定无限锁定命名空间但对给定名称没有太多争用的情况下没有内存压力问题。据我所知,按名称进行的字符串实习锁定会永远增长,并且永远不会被清理并具有其他副作用。互斥锁是半慢的并且有各种烦恼(260 个字符限制)。

4

1 回答 1

1

这将有助于查看确切的代码,IntValue因为那是在AddOrUpdate委托中运行的代码。

我认为问题在于代码期望IntValue实例同时是:

  • 可变以锁定IntValue与每个字符串关联的单个实例(尽管稍后引用计数增加)
  • 不可变,因此将IntValues 与 static进行比较One可作为删除标准

如果我更改代码以IntValue支持不可变零,这似乎可行:

private readonly Func<string, IntObject> _zeroFunc = s => IntObject.Zero;

...

public void RunWithLock(string name, Action body)
{
    name = name.ToLower();
    lock (_lockDict.AddOrUpdate(name, _oneFunc, _incrementFunc))
    {
        body();
        _lockDict.AddOrUpdate(name, _zeroFunc, _decrementFunc);
        _lockDict.TryRemove(name, IntObject.Zero);
    }
}

但我选择以IntValue非操作员的身份实现这样的方法:

    internal IntObject Dec(int p)
    {
        var newVal = Interlocked.Decrement(ref value);
        if (newVal == 0) return Zero;
        return this;
    }
于 2014-12-31T16:04:15.370 回答