C# 对字典的多线程访问
我在网上搜索过,对多线程(C# 对字典的多线程访问,c#,multithreading,thread-safety,C#,Multithreading,Thread Safety,我在网上搜索过,对多线程(lock,Monitor.Enter,volatile等)有点困惑。因此,我没有在这里询问解决方案,而是尝试了一些关于多线程管理的“自制”方法,我想听听你的建议 以下是我的背景: -我有一个静态类,它包含一个静态字典 -我有很多任务(比如说1000)每秒钟阅读一次这本字典 -我有一个另一个任务,每10秒更新一次字典 以下是缓存的代码: public static class Cache { public static bool locked = false;
lock
,Monitor.Enter
,volatile
等)有点困惑。因此,我没有在这里询问解决方案,而是尝试了一些关于多线程管理的“自制”方法,我想听听你的建议
以下是我的背景:
-我有一个静态类,它包含一个静态字典
-我有很多任务(比如说1000)每秒钟阅读一次这本字典
-我有一个另一个任务,每10秒更新一次字典
以下是缓存的代码:
public static class Cache
{
public static bool locked = false;
public static Dictionary<int, string> Entries = new Dictionary<int, string>();
public static Dictionary<int, string> TempEntries = new Dictionary<int, string>();
// Called by 1000+ Tasks
public static string GetStringByTaskId(int taskId)
{
string result;
if (locked)
TempEntries.TryGetValue(taskId, out result);
else
Entries.TryGetValue(taskId, out result);
return result;
}
// Called by 1 task
public static void UpdateEntries(List<int> taskIds)
{
TempEntries = new Dictionary<int, string>(Entries);
locked = true;
Entries.Clear();
try
{
// Simulates database access
Thread.Sleep(3000);
foreach (int taskId in taskIds)
{
Entries.Add(taskId, $"task {taskId} : {DateTime.Now}");
}
}
catch (Exception ex)
{
Log(ex);
}
finally
{
locked = false;
}
}
}
公共静态类缓存
{
公共静态布尔锁定=假;
公共静态字典条目=新字典();
公共静态字典TempEntries=新字典();
//由1000多个任务调用
公共静态字符串GetStringByTaskId(int taskId)
{
字符串结果;
如果(已锁定)
TempEntries.TryGetValue(任务ID,输出结果);
其他的
Entries.TryGetValue(任务ID,输出结果);
返回结果;
}
//由1个任务调用
公共静态void UpdateEntries(列出任务ID)
{
TempEntries=新词典(条目);
锁定=真;
条目。清除();
尝试
{
//模拟数据库访问
睡眠(3000);
foreach(taskIds中的int taskId)
{
Add(taskId,$“task{taskId}:{DateTime.Now}”);
}
}
捕获(例外情况除外)
{
对数(ex);
}
最后
{
锁定=错误;
}
}
}
我的问题是:
程序运行,但我不明白为什么UpdateEntries
方法中两次“锁定”的bool
赋值不会生成多线程异常,因为它“每次”都被另一个线程读取
有没有更传统的方法来处理这个问题,我觉得这是一种奇怪的方法?处理这个问题的传统方法是使用。该类是线程安全的,设计用于多线程对其进行读写。您仍然需要注意潜在的逻辑问题(例如,如果必须同时添加两个键,其他线程才能看到它们中的任何一个),但是对于大多数操作来说,不需要额外锁定就可以了
针对您的特定情况处理此问题的另一种方法是使用普通字典,但一旦读者线程可以使用它,就将其视为不可变的。这将更有效,因为它可以避免锁定
public static void UpdateEntries(List<int> taskIds)
{
//Other threads can't see this dictionary
var transientDictionary = new Dictionary<int, string>();
foreach (int taskId in taskIds)
{
transientDictionary.Add(taskId, $"task {taskId} : {DateTime.Now}");
}
//Publish the new dictionary so other threads can see it
TempEntries = transientDictionary;
}
publicstaticvoidupdateEntries(列出任务ID)
{
//其他线程无法查看此词典
var transientDictionary=新字典();
foreach(taskIds中的int taskId)
{
Add(taskId,$“task{taskId}:{DateTime.Now}”);
}
//发布新词典,以便其他线程可以看到它
TempEntries=瞬时字典;
}
一旦字典被分配到TempEntries
(其他线程唯一可以访问它的地方),它就永远不会被修改,因此线程问题就消失了。处理这个问题的传统方法是使用。该类是线程安全的,设计用于多线程对其进行读写。您仍然需要注意潜在的逻辑问题(例如,如果必须同时添加两个键,其他线程才能看到它们中的任何一个),但是对于大多数操作来说,不需要额外锁定就可以了
针对您的特定情况处理此问题的另一种方法是使用普通字典,但一旦读者线程可以使用它,就将其视为不可变的。这将更有效,因为它可以避免锁定
public static void UpdateEntries(List<int> taskIds)
{
//Other threads can't see this dictionary
var transientDictionary = new Dictionary<int, string>();
foreach (int taskId in taskIds)
{
transientDictionary.Add(taskId, $"task {taskId} : {DateTime.Now}");
}
//Publish the new dictionary so other threads can see it
TempEntries = transientDictionary;
}
publicstaticvoidupdateEntries(列出任务ID)
{
//其他线程无法查看此词典
var transientDictionary=新字典();
foreach(taskIds中的int taskId)
{
Add(taskId,$“task{taskId}:{DateTime.Now}”);
}
//发布新词典,以便其他线程可以看到它
TempEntries=瞬时字典;
}
一旦字典被分配到TempEntries
(其他线程只能访问它的唯一位置),它将永远不会被修改,因此线程问题将消失。使用非易失性bool
标记进行线程同步是不安全的,并且会使代码容易受到竞争条件和冲突的影响。正确的方法是在新词典完全构建之后,使用诸如或方法之类的跨线程发布机制,以原子方式将旧词典替换为新词典。您的案例非常简单,您也可以使用关键字来简化,如下例所示:
public static class Cache
{
private static volatile ReadOnlyDictionary<int, string> _entries
= new ReadOnlyDictionary<int, string>(new Dictionary<int, string>());
public static IReadOnlyDictionary<int, string> Entries => _entries;
// Called by 1000+ Tasks
public static string GetStringByTaskId(int taskId)
{
_entries.TryGetValue(taskId, out var result);
return result;
}
// Called by 1 task
public static void UpdateEntries(List<int> taskIds)
{
Thread.Sleep(3000); // Simulate database access
var temp = new Dictionary<int, string>();
foreach (int taskId in taskIds)
{
temp.Add(taskId, $"task {taskId} : {DateTime.Now}");
}
_entries = new ReadOnlyDictionary<int, string>(temp);
}
}
公共静态类缓存
{
私有静态volatile ReadOnlyDictionary\u条目
=新的只读字典(新字典());
公共静态IReadOnlyDictionary条目=>\u条目;
//由1000多个任务调用
公共静态字符串GetStringByTaskId(int taskId)
{
_TryGetValue(任务ID,输出var结果);
返回结果;
}
//由1个任务调用
公共静态void UpdateEntries(列出任务ID)
{
Thread.Sleep(3000);//模拟数据库访问
var temp=新字典();
foreach(taskIds中的int taskId)
{
临时添加(taskId,$“task{taskId}:{DateTime.Now}”);
}
_条目=新的只读字典(临时);
}
}
使用这种方法,每次访问\u条目
字段都会产生波动性成本,每次操作的波动性成本通常小于10纳秒,因此这应该不是问题。这是一项值得付出的成本,因为它保证了程序的正确性。使用非易失性bool
标志进行线程同步不是线程安全的,并且会使代码容易受到竞争条件和冲突的影响。正确的方法