在讲述这个之前,我们先要弄清楚
为什么需要加锁
竞态条件示例
cs
// 以下操作在多线程中都需要保护:
Dictionary<string, DateTime> dict = new Dictionary<string, DateTime>();
// 线程A
if (!dict.ContainsKey("ip1")) // 检查不存在
// 此时线程B也检查同一个key,也不存在
// 线程A
dict.Add("ip1", time); // 成功添加
// 线程B
dict.Add("ip1", time); // 异常!键已存在
在C#中,当你在操作Dictionary<string, DateTime>
这样的集合时,是否会加锁主要取决于你使用的是哪种类型的Dictionary
。在.NET框架中,有两种主要的Dictionary
实现:
-
**
System.Collections.Generic.Dictionary<TKey, TValue>
** :这是非线程安全的。这意味着如果你在多线程环境下同时对同一个Dictionary
实例进行写操作(如添加、删除、修改元素),你需要自己手动管理线程安全。 -
**
System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>
** :这是线程安全的。ConcurrentDictionary<TKey, TValue>
提供了内置的线程安全机制,允许多个线程同时访问和修改集合而不需要额外的锁。
如果你使用的是System.Collections.Generic.Dictionary<TKey, TValue>
:
如果你决定使用普通的Dictionary
,并且需要在多线程环境下安全地操作它,你有以下几种选择:
新手可能认为 Mutex 更"强大"
cs
public readonly static Mutex mutex = new Mutex(); // 其实过重
而有经验的开发者会用:
cs
private static readonly object _lockObj = new object();
lock(_lockObj) { ... }
Mutex
比 lock
更重量级:
-
Mutex
:内核级对象,上下文切换开销大 -
lock
:用户态同步,性能更好
cs
public static Dictionary<string, DateTime> RemoteLinkStatue = new Dictionary<string, DateTime>();
/// <summary>
/// 读取操作加锁,跨线程
/// </summary>
public readonly static Mutex readRemoteLinkStatueMutex = new Mutex();
App.readRemoteLinkStatueMutex.WaitOne();
try
{
if (App.RemoteLinkStatue.ContainsKey(remoteIp))
App.RemoteLinkStatue[remoteIp] = DateTime.Now;
else
App.RemoteLinkStatue.Add(remoteIp, DateTime.Now);
}
finally
{
App.readRemoteLinkStatueMutex.ReleaseMutex();
}
更好的替代方案
方案1:使用 lock(推荐)
你可以使用lock
语句来确保对字典的访问是线程安全的。例如:
cs
private static readonly object _dictionaryLock = new object();
lock(_dictionaryLock)
{
if (App.RemoteLinkStatue.ContainsKey(remoteIp))
App.RemoteLinkStatue[remoteIp] = DateTime.Now;
else
App.RemoteLinkStatue.Add(remoteIp, DateTime.Now);
}
方案2:使用 ConcurrentDictionary(最佳)
使用ConcurrentDictionary<TKey, TValue>
:如果你经常需要在多线程环境中使用字典,并且对性能有较高要求,可以考虑使用ConcurrentDictionary
。
cs
private static ConcurrentDictionary<string, DateTime> _myConcurrentDictionary = new ConcurrentDictionary<string, DateTime>();
public void AddItem(string key, DateTime value)
{
// 无需手动加锁,方法本身是原子的、线程安全的
_myConcurrentDictionary.TryAdd(key, value);
}
// 原子性操作
RemoteLinkStatus.AddOrUpdate(remoteIp, DateTime.Now, (key, oldValue) => DateTime.Now);
总结**:**
对于纯跨线程的场景,使用 Mutex
是过度设计的
-
单线程环境或只读访问 -> 使用
Dictionary
,无需锁。 -
如果你的应用是多线程的,并且对性能有较高要求,推荐使用
ConcurrentDictionary<TKey, TValue>
。 -
如果出于某种原因你不得不使用普通的
Dictionary<TKey, TValue>
,确保通过适当的同步机制(如锁)来保护对字典的访问。
选择合适的集合类型和同步策略对于确保应用程序的稳定性和性能至关重要