我想让我的代码多线程化,因此我需要把字典变成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
部分之间初始化了。我必须手动锁定这个检查吗?我需要双重锁定吗(null check〉lock〉null check)?
2条答案
按热度按时间pkwftd7m1#
您的担心是有道理的。
Tasks
属性getter不是线程安全的。这里有几个问题。首先,就像你所说的,在一个线程调用
IsEmpty
和从另一个线程移除item之间存在竞争,getter可能返回一个空字典。第二,在
if
检查中读取_lastTaskListRefreshDateTime
和getter末尾的赋值之间存在竞争,即使这些操作是原子的(至少在32位平台上不能是原子的,因为DateTime
是64位的),仍然存在一个微妙的内存障碍问题,因为代码中没有像volatile
这样的同步机制。第三,与我上面的解释类似,
_tasks
引用还有另一个内存障碍问题。一个线程可能调用setter,而另一个线程调用getter。由于不存在内存障碍,CLR或硬件可以自由地优化读写,以使setter中所做的更改对getter不可见。这个问题可能不一定会导致任何问题。但我敢打赌,这是没有预料到的行为。由于没有其他背景进行分析,我不能说任何一种方式。bvjveswy2#
ConcurrentDictionary
只能保证字典的读写不会互相遍历,Dictionary
类不能做到这一点。ConcurrentDictionary
中的线程安全并不能保证你的代码是线程安全的,它只能保证它的代码是线程安全的。既然是这样,你需要在getter中设置一个锁。