C# Dictionary 线程安全指南:多线程下操作 Dictionary<string, DateTime> 的加锁策略

在讲述这个之前,我们先要弄清楚

为什么需要加锁

竞态条件示例

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实现:

  1. ‌**System.Collections.Generic.Dictionary<TKey, TValue>** ‌:这是非线程安全的。这意味着如果你在多线程环境下同时对同一个Dictionary实例进行写操作(如添加、删除、修改元素),你需要自己手动管理线程安全。

  2. ‌**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>,确保通过适当的同步机制(如锁)来保护对字典的访问。

选择合适的集合类型和同步策略对于确保应用程序的稳定性和性能至关重要

相关推荐
Flynn Liang11 分钟前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
灿飞18 分钟前
Kotlin与Java虚拟机六:基础与类型系统:一切皆对象,但对象有代价
java·kotlin
计算机毕设定制辅导-无忧学长27 分钟前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统
亚远景aspice29 分钟前
亚远景 - 无方向盘 L4 车辆批量认证,ASPICE 过程证据成制造商安全举证底座
安全·自动驾驶·aspice·研发管理·亚远景科技·过程能力评估·aspice评估
可涵不会debug44 分钟前
PHP2Shell 爆发一周后:CVE-2026-109618/109619 自查、修复与防护实录
安全·php·云安全·waf·phpshell
WarPigs1 小时前
C#把byte[]以十六进制打印,带分割符
c#
福大大架构师每日一题1 小时前
excelize v2.11.1发布:Go 1.26.0起步,16项安全修复与嵌入对象能力全面升级
开发语言·安全·golang
雪芽蓝域zzs1 小时前
第 1 章:IDEA 创建 SpringBoot3.5.16 Maven 项目
java·maven·intellij-idea
golang学习记1 小时前
IDEA 开始换芯,发布全新AI版终端
java·人工智能·intellij-idea
yunlaodacom2 小时前
做异地灾备,华为云国际站 OBS 跨地域复制该怎么配置?
安全·华为云·云计算