atomic addorupdate(尝试使用并发字典编写命名的locker)

时间:2013-06-25 00:20:31

标签: .net concurrency concurrentdictionary

ConcurrentDictionary Pitfall - Are delegates factories from GetOrAdd and AddOrUpdate synchronized?注意到AddOrUpdate不是原子的(并且不能保证代理不会多次运行)。

我正在尝试使用并发字典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)使用条件删除提到了here。此外,IntObject是一个简单的int可变对象包装器。

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

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

1 个答案:

答案 0 :(得分:1)

查看IntValue的确切代码会有所帮助,因为这是AddOrUpdate委托中运行的代码。

我认为问题是代码期望IntValue个实例都是:

  • 可变,以便锁定与每个字符串关联的单个IntValue实例(尽管引用计数稍后递增)
  • 不可变,以便将IntValue与静态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;
    }