Atomic addorupdate (attempt to write a named blocker using a parallel dictionary)

ConcurrentDictionary Pitfall - Are delegated factories from GetOrAdd and AddOrUpdate synchronized? notes that AddOrUpdate is not atomic (and cannot guarantee that delegates will be run more than once).

I am trying to implement a name block implementation using a parallel dictionary a la here , but where the dictionary should not grow forever, for example:

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); } } } 

But the problem is that AddOrUpdate is not atomic, so I see that often records are not deleted when there is competition. I am sure, however, that if AddOrUpdate was atomic, the above code would do its job and the records would be deleted accordingly.

Note the use of conditional deletion by the key extension method + val TryRemove (key, val) mentioned here . In addition, IntObject is a simple replaceable wrapper for an int object.

What are my options? Are there any parallel dictionary implementations that have 1. atomic conditional (by key and value) deletion and 2. AddOrUpdate is atomic and ensures that delegates are not run more than once?

Are there any other ideas? I would like the named locker to be fast, but not have problems with memory pressure, given the unlimited namespace of the names, but with a little controversy over the given name. As far as I know, lowercase interning by name grows forever and never gets cleaned up and has other side effects. And mutexes are semi-slow and have various annoyances (260 char limit).

+7
source share
1 answer

This will help you see the exact code for IntValue , as this is the code executed by the AddOrUpdate delegate.

I think the problem is that the code expects IntValue instances for both:

  • Mutable to block a single IntValue instance associated with each row (despite increasing the number of links at the end)
  • Immutable, so comparing IntValue with static One works as a delete criterion

If I change the code so that IntValue supports immutable zero, this seems to work:

 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); } } 

But I decided to implement IntValue methods similar to this, and as non-operators:

  internal IntObject Dec(int p) { var newVal = Interlocked.Decrement(ref value); if (newVal == 0) return Zero; return this; } 
+1
source

All Articles