Is the ConcurrentDictionary thread-safe to the point that I can use it for a static cache?

问题:

Basically, if I want to do the following:

  1. public class SomeClass
  2. {
  3. private static ConcurrentDictionary<..., ...> Cache { get; set; }
  4. }

Does this let me avoid using locks all over the place?

解答:

Yes, it is thread safe and yes it avoids you using locks all over the place (whatever that means). Of course that will only provide you a thread safe access to the data stored in this dictionary, but if the data itself is not thread safe then you need to synchronize access to it of course. Imagine for example that you have stored in this cache a List<T>. Now thread1 fetches this list (in a thread safe manner as the concurrent dictionary guarantees you this) and then starts enumerating over this list. At exactly the same time thread2 fetches this very same list from the cache (in a thread safe manner as the concurrent dictionary guarantees you this) and writes to the list (for example it adds a value). Conclusion: if you haven't synchronized thread1 it will get into trouble.

As far as using it as a cache is concerned, well, that's probably not a good idea. For caching I would recommend you what is already built into the framework. Classes such as MemoryCache for example. The reason for this is that what is built into the System.Runtime.Caching assembly is, well, explicitly built for caching => it handles things like automatic expiration of data if you start running low on memory, callbacks for cache expiration items, and you would even be able to distribute your cache over multiple servers using things like memcached, AppFabric, ..., all things that you would can't dream of with a concurrent dictionary.

Is the MemoryCache class thread-safe like the ConcurrentDictionary is though? – michael

@michael, yes it is thread safe but absolutely the same remark stands true about synchronizing access to non thread-safe objects that you might be storing into this cache. – Darin Dimitrov

Oh, I understand that part. But, just so that other readers can understand I'm going to reiterate it. You're saying that both the ConcurrentDictionary and MemoryCache class are thread-safe, but the contents within are not guaranteed to be thread-safe. :) – michael

@michael, exactly, nicely put. My English is so poor that I can't express myself. – Darin Dimitrov

ConcurrentDictionary

  1. internal class Program
  2. {
  3. private static void Main()
  4. {
  5. try
  6. {
  7. var dictionary = new ConcurrentDictionary<string, int>();
  8. for (char c = 'A'; c <= 'F'; c++)
  9. {
  10. dictionary.TryAdd(c.ToString(), c - 'A' + );
  11. }
  12.  
  13. var iterationTask = new Task(() =>
  14. {
  15. foreach (var pair in dictionary)
  16. {
  17. Console.WriteLine(pair.Key + ":" + pair.Value);
  18. }
  19. });
  20.  
  21. var updateTask = new Task(() =>
  22. {
  23. foreach (var pair in dictionary)
  24. {
  25. dictionary[pair.Key] = pair.Value + ;
  26. }
  27. });
  28.  
  29. var addTask = new Task(() =>
  30. {
  31. for (char c = 'G'; c <= 'K'; c++)
  32. {
  33. //dictionary[c.ToString()] = c - 'A' + 1;
  34. bool flag = dictionary.TryAdd(c.ToString(), c - 'A' + );
  35. if (!flag)
  36. {
  37. Console.WriteLine("添加{0}失败", c);
  38. }
  39. else
  40. {
  41. Console.WriteLine("添加{0}成功", c);
  42. }
  43. }
  44. });
  45.  
  46. var deleteTask=new Task(() =>
  47. {
  48. foreach (var pair in dictionary)
  49. {
  50. if (Convert.ToChar(pair.Key) <= 'F')
  51. {
  52. int number;
  53. bool flag = dictionary.TryRemove(pair.Key, out number);
  54. if (!flag)
  55. {
  56. Console.WriteLine("移除{0}失败", pair.Key);
  57. }
  58. else
  59. {
  60. Console.WriteLine("移除{0}成功", pair.Key);
  61. }
  62. }
  63. }
  64. });
  65.  
  66. updateTask.Start();
  67. iterationTask.Start();
  68. addTask.Start();
  69. deleteTask.Start();
  70. Task.WaitAll(updateTask, iterationTask,addTask,deleteTask);
  71.  
  72. }
  73. catch (Exception ex)
  74. {
  75. while (ex != null)
  76. {
  77. Console.WriteLine(ex.Message);
  78. ex = ex.InnerException;
  79. }
  80. }
  81. Console.ReadLine();
  82. }
  83. }

执行结果几乎每次都不相同,但是总能成功执行。

Dictionary

非线程安全的,代码执行的时候,会提示,集合已修改

  1. internal class Program
  2. {
  3. private static void Main()
  4. {
  5. try
  6. {
  7. var dictionary = new Dictionary<string, int>();
  8. for (char c = 'A'; c <= 'F'; c++)
  9. {
  10. dictionary.Add(c.ToString(), c - 'A' + );
  11. }
  12.  
  13. var iterationTask = new Task(() =>
  14. {
  15. foreach (var pair in dictionary)
  16. {
  17. Console.WriteLine(pair.Key + ":" + pair.Value);
  18. }
  19. });
  20.  
  21. var updateTask = new Task(() =>
  22. {
  23. foreach (var pair in dictionary)
  24. {
  25. dictionary[pair.Key] = pair.Value + ;
  26. }
  27. });
  28.  
  29. var addTask = new Task(() =>
  30. {
  31. for (char c = 'G'; c <= 'K'; c++)
  32. {
  33. dictionary.Add(c.ToString(), c - 'A' + );
  34. }
  35. });
  36.  
  37. var deleteTask = new Task(() =>
  38. {
  39. foreach (var pair in dictionary)
  40. {
  41. if (Convert.ToChar(pair.Key) <= 'F')
  42. {
  43. bool flag = dictionary.Remove(pair.Key);
  44. if (!flag)
  45. {
  46. Console.WriteLine("移除{0}失败", pair.Key);
  47. }
  48. else
  49. {
  50. Console.WriteLine("移除{0}成功", pair.Key);
  51. }
  52. }
  53. }
  54. });
  55.  
  56. updateTask.Start();
  57. iterationTask.Start();
  58. addTask.Start();
  59. deleteTask.Start();
  60. Task.WaitAll(updateTask, iterationTask, addTask, deleteTask);
  61.  
  62. }
  63. catch (Exception ex)
  64. {
  65. while (ex != null)
  66. {
  67. Console.WriteLine(ex.Message);
  68. ex = ex.InnerException;
  69. }
  70. }
  71. Console.ReadLine();
  72. }
  73. }

线程不安全的方法

http://www.cnblogs.com/chucklu/p/4468057.html

https://stackoverflow.com/questions/51138333/is-this-concurrentdictionary-thread-safe

No; the dictionary could change between ContainsKey() & TryAdd().

You should never call two methods in a row on ConcurrentDictionary, unless you're sure you don't care if it changes between them.
Similarly, you can't loop through the dictionary, since it might change during the loop.

Instead, you should use its more-complex methods (like TryAdd(), which will check and add in a single atomic operation.

Also, as you suggested, the entire dictionary might be replaced.

https://stackoverflow.com/questions/38323009/which-members-of-nets-concurrentdictionary-are-thread-safe

There is a specific section in the documentation that makes clear not everything is thread-safe in the ConcurrentDictionary<TKey, TValue>:

All these operations are atomic and are thread-safe with regards to all other operations on the ConcurrentDictionary<TKey, TValue> class. The only exceptions are the methods that accept a delegate, that is, AddOrUpdate and GetOrAdd. For modifications and write operations to the dictionary, ConcurrentDictionary<TKey, TValue> uses fine-grained locking to ensure thread safety. (Read operations on the dictionary are performed in a lock-free manner.) However, delegates for these methods are called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, the code executed by these delegates is not subject to the atomicity of the operation.

So there are some general exclusions and some situations specific to ConcurrentDictionary<TKey, TValue>:

  • The delegates on AddOrUpdate and GetOrAdd are not called in a thread-safe matter.
  • Methods or properties called on an explicit interface implementation are not guaranteed to be thread-safe;
  • Extension methods called on the class are not guaranteed to be thread-safe;
  • All other operations on public members of the class are thread-safe.

最后是用MemoryCache来处理?

https://stackoverflow.com/questions/21269170/locking-pattern-for-proper-use-of-net-memorycache

This is my 2nd iteration of the code. Because MemoryCache is thread safe you don't need to lock on the initial read, you can just read and if the cache returns null then do the lock check to see if you need to create the string. It greatly simplifies the code.

  1. const string CacheKey = "CacheKey";
  2. static readonly object cacheLock = new object();
  3. private static string GetCachedData()
  4. {
  5.  
  6. //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
  7. var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;
  8.  
  9. if (cachedString != null)
  10. {
  11. return cachedString;
  12. }
  13.  
  14. lock (cacheLock)
  15. {
  16. //Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
  17. cachedString = MemoryCache.Default.Get(CacheKey, null) as string;
  18.  
  19. if (cachedString != null)
  20. {
  21. return cachedString;
  22. }
  23.  
  24. //The value still did not exist so we now write it in to the cache.
  25. var expensiveString = SomeHeavyAndExpensiveCalculation();
  26. CacheItemPolicy cip = new CacheItemPolicy()
  27. {
  28. AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes())
  29. };
  30. MemoryCache.Default.Set(CacheKey, expensiveString, cip);
  31. return expensiveString;
  32. }
  33. }
  1.  

EDIT: The below code is unnecessary but I wanted to leave it to show the original method. It may be useful to future visitors who are using a different collection that has thread safe reads but non-thread safe writes (almost all of classes under the System.Collections namespace is like that).

Here is how I would do it using ReaderWriterLockSlim to protect access. You need to do a kind of "Double Checked Locking" to see if anyone else created the cached item while we where waiting to to take the lock.

  1. const string CacheKey = "CacheKey";
  2. static readonly ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
  3. static string GetCachedData()
  4. {
  5. //First we do a read lock to see if it already exists, this allows multiple readers at the same time.
  6. cacheLock.EnterReadLock();
  7. try
  8. {
  9. //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
  10. var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;
  11.  
  12. if (cachedString != null)
  13. {
  14. return cachedString;
  15. }
  16. }
  17. finally
  18. {
  19. cacheLock.ExitReadLock();
  20. }
  21.  
  22. //Only one UpgradeableReadLock can exist at one time, but it can co-exist with many ReadLocks
  23. cacheLock.EnterUpgradeableReadLock();
  24. try
  25. {
  26. //We need to check again to see if the string was created while we where waiting to enter the EnterUpgradeableReadLock
  27. var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;
  28.  
  29. if (cachedString != null)
  30. {
  31. return cachedString;
  32. }
  33.  
  34. //The entry still does not exist so we need to create it and enter the write lock
  35. var expensiveString = SomeHeavyAndExpensiveCalculation();
  36. cacheLock.EnterWriteLock(); //This will block till all the Readers flush.
  37. try
  38. {
  39. CacheItemPolicy cip = new CacheItemPolicy()
  40. {
  41. AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes())
  42. };
  43. MemoryCache.Default.Set(CacheKey, expensiveString, cip);
  44. return expensiveString;
  45. }
  46. finally
  47. {
  48. cacheLock.ExitWriteLock();
  49. }
  50. }
  51. finally
  52. {
  53. cacheLock.ExitUpgradeableReadLock();
  54. }
  55. }

ConcurrentDictionary和Dictionary的更多相关文章

  1. ConcurrentDictionary与Dictionary 替换

    本文导读:ASP.NET中ConcurrentDictionary是.Net4 增加的,相对于Dictionary的线程安全的集合, ConcurrentDictionary可实现一个线程安全的集合, ...

  2. ConcurrentDictionary 与 Dictionary

    ASP.NET中ConcurrentDictionary是.Net4 增加的,与 Dictionary 最主要区别是, 前者是线程安全的集合,可以由多个线程同时并发读写Key-value.   那么 ...

  3. ConcurrentDictionary 对决 Dictionary+Locking

    在 .NET 4.0 之前,如果我们需要在多线程环境下使用 Dictionary 类,除了自己实现线程同步来保证线程安全之外,我们没有其他选择. 很多开发人员肯定都实现过类似的线程安全方案,可能是通过 ...

  4. 浅谈ConcurrentDictionary与Dictionary

    在.NET4.0之前,如果我们需要在多线程环境下使用Dictionary类,除了自己实现线程同步来保证线程安全外,我们没有其他选择.很多开发人员肯定都实现过类似的线程安全方案,可能是通过创建全新的线程 ...

  5. 改进ConcurrentDictionary并行使用的性能

    上一篇文章“ConcurrentDictionary 对决 Dictionary+Locking”中,我们知道了 .NET 4.0 中提供了线程安全的 ConcurrentDictionary< ...

  6. ConcurrentDictionary<TKey, TValue>的AddOrUpdate方法

    https://msdn.microsoft.com/zh-cn/library/ee378665(v=vs.110).aspx 此方法有一共有2个,现在只讨论其中一个 public TValue A ...

  7. ConcurrentDictionary并发字典知多少?

    背景 在上一篇文章你真的了解字典吗?一文中我介绍了Hash Function和字典的工作的基本原理. 有网友在文章底部评论,说我的Remove和Add方法没有考虑线程安全问题. https://doc ...

  8. 1、C#中Hashtable、Dictionary详解以及写入和读取对比

    在本文中将从基础角度讲解HashTable.Dictionary的构造和通过程序进行插入读取对比. 一:HashTable 1.HashTable是一种散列表,他内部维护很多对Key-Value键值对 ...

  9. Encountered an unexpected error when attempting to resolve tag helper directive '@addTagHelper' with value '"*, Microsoft.AspNet.Mvc.TagHelpers"'

    project.json 配置: { "version": "1.0.0-*", "compilationOptions": { " ...

随机推荐

  1. centos crontab 定时任务详解

    安装crontab: yum install crontabs 说明: /sbin/service crond start //启动服务 /sbin/service crond stop //关闭服务 ...

  2. 分布式文件系统 - FastDFS

    分布式文件系统 - FastDFS 别问我在哪里 也许我早已不是我自己,别问我在哪里,我一直在这里. 突然不知道说些什么了... 初识 FastDFS 记得那是我刚毕业后进入的第一家公司,一个技术小白 ...

  3. openstack安装、卸载与启动

    一.安装: 更新: sudo apt-get update sudo apt-get upgrade 安装图形化界面: sudo apt-get install ubuntu-desktop 安装gc ...

  4. 小王子浅读Effective javascript(一)了解javascript版本

    哈哈,各位园友新年快乐!愚安好久没在园子里写东西了,这次决定针对javascript做一个系列,叫做<小王子浅读Effective javascript>,主要是按照David Herma ...

  5. 怎么查看其它apk里面的布局代码及资源

    今天才看到的好方法, 将你要的apk文件的后缀名改为zip,解压就可以了. --------------------------------- 提示:有时候系统会自动隐藏你的后缀名的,这时候就需要你将 ...

  6. ffmpeg 打开视频流太慢(下)

    前面的博文中已经交代过,ffmpeg打开视频慢主要是因为av_find_stream_info 耗时久.下面给出重写查找音视频stream info的一段代码,用来替代av_find_stream_i ...

  7. Wireshark - ICMP 报文分析

    1. 测试机器,源 IP 地址为 10.21.28.110,目的 IP 地址为 10.6.0.24. 2. 使用 "ip.addr == 10.6.0.24 and icmp" 过 ...

  8. ssh连接慢

    suse刚装完,开始用ssh的时候,总会遇到这样的问题:输入了用户名以后,等半天才出输入密码的框,很是急人.这是dns反查造成的.解决方法:编辑 /etc/ssh/sshd_conf , 将 #Use ...

  9. Unity3D研究院之Machine动画脚本自动生成AnimatorController

    原地址: http://www.xuanyusong.com/archives/2811 以前的项目一直不敢用Machine动画,因为当时立项的时候Machine动画还不成熟,最近项目做得差不多了我能 ...

  10. rabbitMQ安装及部署

    1.安装 rpm -ivh erlang-18.3-1.el6.x86_64.rpm, 下载地址:http://www.rabbitmq.com/releases/erlang/ rpm --impo ...