将字典重构为并发字典
本文关键字:字典 并发 重构 | 更新日期: 2023-09-27 17:56:53
我想让我的代码多线程,因此我需要将字典更改为ConcurrentDictionary
。我阅读了有关ConcurrentDictionary
的信息,检查了一些示例,但我仍然需要帮助:
这是原始代码(用于单线程)
private IDictionary<string, IDictionary<string, Task>> _tasks;
public override IDictionary<string, IDictionary<string, Task>> Tasks
{
get
{
// return dictionary from cache unless too old
// concurrency!! (null check)
if (_tasks != null && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
{
return _tasks;
}
// reload dictionary from database
_tasks = new Dictionary<string, IDictionary<string, Task>>();
// find returns an IEnumerable<Task>
var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();
// build hierarchical dictionary from flat IEnumerable
// concurrency!!
foreach (var t in tasks)
{
if (_tasks.ContainsKey(t.Area.Key))
{
if (_tasks[t.Area.Key] == null)
{
_tasks[t.Area.Key] = new Dictionary<string, Task>();
}
if (!_tasks[t.Area.Key].ContainsKey(t.Key))
{
_tasks[t.Area.Key].Add(t.Key, t);
}
}
else
{
_tasks.Add(t.Area.Key, new Dictionary<string, Task> { { t.Key, t } });
}
}
_lastTaskListRefreshDateTime = DateTime.Now;
return _tasks;
}
set
{
_tasks = value;
}
}
这是我想到的:
private ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> _tasks = new ConcurrentDictionary<string, ConcurrentDictionary<string, Task>>();
public override ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> Tasks
{
get
{
// use cache
// concurrency?? (null check)
if (!_tasks.IsEmpty && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
{
return _tasks;
}
// reload
var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();
foreach (var task in tasks)
{
var t = task; // inner scope for clousure
var taskKey = t.Key;
var areaKey = t.Area.Key;
var newDict = new ConcurrentDictionary<string, Task>();
newDict.TryAdd(taskKey, t);
_tasks.AddOrUpdate(areaKey, newDict, (k, v) => {
// An dictionary element if key=areaKey already exists
// extend and return it.
v.TryAdd(taskKey, t);
return v;
});
}
_lastTaskListRefreshDateTime = DateTime.Now;
return _tasks;
}
}
我不太确定是这样,特别是我很确定IsEmpty
检查不是线程安全的,因为_tasks
可能已在IsEmpty
检查和&& ...
部分或return _tasks
部分之间初始化。我必须手动锁定此检查吗?我需要双重锁(空检查>锁>空检查)吗?
你的担心是有道理的。Tasks
属性 getter 不是线程安全的。这里有几个问题。
首先,就像你这边一样,从一个线程调用IsEmpty
和从另一个线程中删除项目之间存在竞争。getter 可以返回一个空字典。
其次,在if
检查中读取_lastTaskListRefreshDateTime
和在获取器结束时进行分配之间存在竞争。即使这些操作是原子的(至少在 32 位平台上不能这样做,因为DateTime
是 64 位),仍然存在一个微妙的内存屏障问题,因为代码中没有明显的volatile
同步机制。
第三,与我上面的解释类似,_tasks
参考还有另一个记忆障碍问题。一个线程可以调用 setter,而另一个线程正在调用 getter。由于不存在内存屏障,因此 CLR 或硬件可以自由地优化读取和写入,以使 getter 看不到在 setter 中所做的更改。这个问题不一定会导致任何问题,但我敢打赌这是没有预料到的行为。由于没有其他分析背景,我不能说任何一种方式。
ConcurrentDictionary
只保证读取和写入字典不会相互遍历,这是Dictionary
类不会做的。ConcurrentDictionary
中的线程安全不会使代码线程安全,它只会确保其代码是线程安全的。在这种情况下,您将需要锁定您的吸气器。