将字典重构为 ConcurrentDictionary
我想让我的代码多线程化,因此我需要将字典更改为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
部分。我必须手动锁定此支票吗?我需要双重锁(空检查>锁>空检查)吗?
I want to make my code multithreadable, therefor i need to change a Dictionary into a ConcurrentDictionary
. I read about the ConcurrentDictionary
, checked some example, but still I need a hand on this:
Here is the original code (for single thread)
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;
}
}
Here is what I came up with:
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;
}
}
I'm not so sure this is it, in particular i am quite sure that the IsEmpty
check is not threadsafe since the _tasks
may have been initialized between the IsEmpty
check and the && ...
part or the return _tasks
part. Do I have to lock this check manually? Do i need a double lock (null check > lock > null check) ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
你的担心是有道理的。
Tasks
属性 getter 不是线程安全的。这里有几个问题。首先,像您一样,从一个线程调用
IsEmpty
与从另一个线程删除项目之间存在竞争。 getter 可以返回一个空字典。其次,在
if
检查中读取_lastTaskListRefreshDateTime
与 getter 末尾的赋值之间存在竞争。即使这些操作是原子的(至少在 32 位平台上它们不能是原子的,因为DateTime
是 64 位),仍然存在一个微妙的内存屏障问题,因为没有像volatile< 这样的同步机制/code> 在代码中很明显。
第三,与我上面的解释类似,
_tasks
引用还存在另一个内存障碍问题。一个线程可以调用 setter,而另一个线程则调用 getter。由于不存在内存屏障,CLR 或硬件可以自由地优化读取和写入,从而使 setter 中所做的更改对 getter 不可见。此问题不一定会导致任何问题,但我敢打赌这是未预料到的行为。由于没有其他背景可供分析,我不能说任何一种方式。Your concern is justified. The
Tasks
property getter is not thread-safe. There are a few issues here.First, like you side, there is a race between a call to
IsEmpty
from one thread and the removal of item from another thread. The getter could return an empty dictionary.Second, there is a race between the read of
_lastTaskListRefreshDateTime
in theif
check and the assignment at the end of the getter. Even if these operations are atomic (which they cannot be at least on 32-bit platforms sinceDateTime
is 64-bits) there is still a subtle memory barrier issue since no synchronization mechanisms likevolatile
are apparent in the code.Third, similar to my explanation above, there is another memory barrier problem with
_tasks
reference. One thread could call the setter while another is calling the getter. Since no memory barrier is present the CLR or hardware are free to optimize the reads and writes in such a manner that the changes made in the setter are not visible to the getter. This issue might not necessarily cause any problems, but I bet it is behavior that was not anticipated. With no other context for analysis I cannot say either way.ConcurrentDictionary
仅保证对字典的读取和写入不会互相遍历,而Dictionary
类则不会这样做。 ConcurrentDictionary 中的线程安全并不使您的代码线程安全,它仅确保其代码是线程安全的。在这种情况下,您需要在 getter 中加一把锁。The
ConcurrentDictionary
only guarantees that reads and writes into the dictionary will not walk all over each other, something theDictionary
class does not do. The thread safety in theConcurrentDictionary
does not make your code thread safe, it only ensures its code is thread safe. Since this is the case you will need a lock in your getter.