0

I'm not sure it is so simple question, as it's stated in: When should I use ConcurrentDictionary and Dictionary?.

In my case, I have only one thread which calls Add to add items, and several threads calls Remove to remove items (one per item, actually Timers). There are many reading threads. Also, my logic ensures that items to be deleted are not in use anymore (time based key), i.e. it would not read them.

Also, it should handle a very high input rate.

So maybe Dictionary is better here, because it has a better performance, and there's no real concurrent write on the same resource (I'm not sure). After all, it's not a linked list type of collection that needs to update pointers between items upon any change. Are concurrent Add and Get may be buggy in such case with a non thread-safe Dictionary?

In other words, is the Add operation non-atomic in a way that Get operation for the same key would return a buggy object because the Dictionary has a non stable state? Maybe it's enough to sync the Remove operations only?

Update: Notice I have only 1 thread that adds items, several Timer threads that delete a single item each (it must be an item which was already added), and Get operations are not done on items which are behind the time scope (those which are to be deleted). Also, The single adding thread makes a list of already-added items to be read by the handling reading threads. That's why I think it may work without any lock (or only lock for the Remove operations).

Amir
  • 391
  • 2
  • 3
  • 10
  • The underlying collection used by the dictionary gets changed and that will cause exceptions when the collection is iterated which is done when looking for or adding a key. Dictionary is not threadsafe. If you need thread safety use concurrent dictionary or provide your own thread safety with locks. – rene May 01 '22 at 06:15
  • 2
    *it's not a linked list type of collection* - actually.. (you should take a look at how it resolves hash collisions) – Caius Jard May 01 '22 at 06:17
  • Does this answer your question? [Thread safety of a Dictionary](https://stackoverflow.com/questions/2043615/thread-safety-of-a-dictionarytkey-tvalue) – Charlieface May 01 '22 at 10:52

1 Answers1

-2

I think you can optimize this reducing the amount of locks in the dictionary. The key is find a condition to locale an interval in which you modify the dictionary and ensure that you don't go outside this interval while you are updating the dictionary.

For example, suppose that you need 10 minutes to update the dictionary. And you decide update dictionary between 10:00 to 11:00 each day. Yo can do the update at 10:00.

Then, from 11:00 to 10:00 of next day, you can work with your dictionary without make any lock. Between 10:00 to 11:00, you must use lock always because maybe your dicctionay will change.

In this example it's impossible that you alter the dictionary without locks because it's happen only if you need more than 1 hour in update your dictionary.

The lock, then, is a real lock in that hour and a DateTime check the other 23 hours. I don't know if can adapt your case to this focus but if you can, you get a better performance for sure.

You can encapsulate this logic in a method:

private void DoWithDictionary(Action action)
{
    var now = DateTime.UtcNow;
    if (now.Hour == 10)
    {
        action();
    }
    else
    {
        lock (this._dict)
        {
            action();
        }
    }
}

private T DoWithDictionary<T>(Func<T> action)
{
    var now = DateTime.UtcNow;
    if (now.Hour == 10)
    {
        return action();
    }
    else
    {
        lock (this._dict)
        {
            return action();
        }
    }
}

And use it:

var keyToGet = 17;
var value = DoWithDictionary(() => this._dict[keyToGet]);

DoWithDictionary(() =>
{
    if (this._dict.TryGetValue(keyToGet, out string text))
    {
        //...
    }
});

var keyToRemove = 17;
DoWithDictionary(() => this._dict.Remove(keyToRemove));

UPDATE

I think that my response is misunderstood. At the end, is similiar to enqueue changes and execute each time all in one batch.

For example, suppose a thread do 4 deletes in first second and 2 deletes in next second. That thread do 6 locks. The thread may have a list in which save all removes each second. So in first second, the thread adds 4 items. When the second finish, the thread lock the dictionary and remove 4 items with only one lock. In next second, one extra lock for the 2 items to remove. So, 2 locks instead of 6.

I think this make sense and it's something that I usually do to get better performance. And also, with database access o sockets send: you add information and when buffer if full or some time expired, you send all information.

Here, the problem is that you have lots of threads: you can do the same approach but only get better performance to thread level. If you can get some logic (like my 23/1 hours, that is only a trivial sample and diffilcult to use in real world) in which you can avoid the locks in all threads, you get better performance. At the end, you do the lock to get exclusive access to the resource. In my sample, I know that I can access (not modify) to the dictionary without a lock and it's thread safe because I know (without a real lock sentence) that it's safe to access the dictionary in these hours.

Victor
  • 2,313
  • 2
  • 5
  • 13
  • I can't really fathom what scenario this would be useful or thread safe in. You're essentially saying that for 1 hour, only one thread should modify the dictionary at a time, but that that won't be enforced. – ProgrammingLlama May 01 '22 at 09:23
  • No @DiplomacyNotWar, I'm saying that in 1 hour all threads do a real lock to access to the dictionary (thread safe) and rest of hours no one do lock (faster access) because none of them modify the dictionary. It's only a sample, Amir must look for the condition/interval that may adapt to it's scenary. I suppose that he can't wait 23 hours to update the dictionary but I don't know his problem, I can't give him a concrete sample for his case. – Victor May 01 '22 at 09:38
  • But each thread removes items. That's modifying the dictionary. – ProgrammingLlama May 01 '22 at 09:41
  • Yes, and these threads, in my sample, remove items between 10 to 11. And the thread that adds items do in that hour too. Rest of time, all threads can access to dictionary thread safe without do lock. Is for that reason I tell that Amir must try to get an interval valid in his problem. May be not possible for his problem but if he can find some logic in which he knows no one thread alter the dictionary, he can skip lock for these access. – Victor May 01 '22 at 09:46
  • 1
    The performance of a solution is only relevant when the correctness is guaranteed. No one cares how fast you can process the data, if the processing is not reliable. So you should address this issue first and foremost. How does your approach guarantees that the behavior of the clock-synchronized `Dictionary` is well defined? Please link to the relevant part of [the documentation](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2) where this guarantee is explicitly specified. – Theodor Zoulias May 01 '22 at 13:02
  • Your solution doesn't fit my needs. This is not the case. Notice I have only 1 thread that adds items, several Timer threads that delete a single item each (it must be an item which was already added), and Get operations are not done on items which are behind the time scope (those which are to be deleted) – Amir May 01 '22 at 13:17
  • @TheodorZoulias Q1: if you never modify the dictionary, is safe access to it without lock? Q2: if you lock the access both to read and modify, is safe? If you respond yes, my answer is safe. The clock is the same for all threads and you may allow modification since 10:00:02 if you want more fiability. – Victor May 01 '22 at 16:00
  • @Amir can you give more details about the business logic? Maybe easier help you if I know a bit more the problem. – Victor May 01 '22 at 16:03
  • @Amir are you adding a timer each time you add a new item to the dictionary, to remove it after some time? – Victor May 01 '22 at 17:22