C#此方法是线程安全的吗?
考虑以下代码:C#此方法是线程安全的吗?,c#,multithreading,C#,Multithreading,考虑以下代码: Dictionary<string, string> list = new Dictionary<string, string>(); object lockObj = new object(); public void MyMethod(string a) { if (list.Contains(a)) return; lock (lockObj) { list.Add(a,"someotherval
Dictionary<string, string> list = new Dictionary<string, string>();
object lockObj = new object();
public void MyMethod(string a) {
if (list.Contains(a))
return;
lock (lockObj) {
list.Add(a,"someothervalue");
}
}
字典列表=新字典();
object lockObj=新对象();
公共void MyMethod(字符串a){
如果(列表包含(a))
返回;
锁(lockObj){
列表。添加(a,“其他价值”);
}
}
假设我同时从不同的线程调用MyMethod(“mystring”)
是否可能有多个线程(我们将其视为两个)同时输入if
(!list.Contains(a))
语句(有几个CPU周期差异),两个线程的计算结果都是false
,一个线程进入关键区域,而另一个线程被锁定在外部,因此,第二个线程进入并在第一个线程退出后再次将“mystring”添加到列表中,导致字典试图添加重复的密钥?不,这不是线程安全的。您需要在列表周围加锁。它也包含,因为在if测试和添加数据之间,一个线程可能会被调出,然后又被调回。另一个线程可能同时添加了数据。您需要锁定整个语句。您可能会在.Contains
部分遇到问题(您现在的代码方式)锁定后应检查列表。e、 g
if (list.Contains(a))
return;
lock (lockObj) {
if (list.Contains(a))
return;
list.Add(a);
}
}
private Dictionary list=new Dictionary();
公共void MyMethod(字符串a){
锁(列表){
如果(列表包含(a))
返回;
列表。添加(a,“其他价值”);
}
}
看看这本指南,很好
需要牢记的一些准则
当锁定多个可写值时,通常锁定私有静态对象
不要锁定作用域在类或本地方法之外的对象,例如lock(this)
,这可能会导致死锁李>
如果对象是唯一同时访问的对象,则可以锁定正在更改的对象
确保您锁定的对象不为空李>
只能锁定引用类型
您可以首先执行非锁定检查,但如果希望线程安全,则需要在锁内再次重复该检查。这样你就不会锁定,除非你必须这样做,并确保线程安全
Dictionary<string, string> list = new Dictionary<string, string>();
object lockObj = new object();
public void MyMethod(string a) {
if (list.Contains(a))
return;
lock (lockObj) {
if (!list.Contains(a)){
list.Add(a,"someothervalue");
}
}
}
字典列表=新字典();
object lockObj=新对象();
公共void MyMethod(字符串a){
如果(列表包含(a))
返回;
锁(lockObj){
如果(!list.Contains(a)){
列表。添加(a,“其他价值”);
}
}
}
您需要锁定整个操作(检查并添加),否则多个线程可能会尝试添加相同的值
我建议使用,因为它是线程安全的
private readonly ConcurrentDictionary<string, string> _items
= new ConcurrentDictionary<string, string>();
public void MyMethod(string item, string value)
{
_items.AddOrUpdate(item, value, (i, v) => value);
}
专用只读ConcurrentDictionary\u项
=新的ConcurrentDictionary();
公共void MyMethod(字符串项、字符串值)
{
_添加或更新(项目,值,(i,v)=>值);
}
我假设你的意思是写ContainsKey
而不是Contains
<代码>包含字典上的是显式实现的,因此无法通过您声明的类型访问它。1
你的代码不安全。原因是没有任何东西阻止同时执行ContainsKey
和Add
。实际上,这会引入一些非常显著的故障场景。因为我研究了字典
是如何实现的,所以我可以看到您的代码可能会导致数据结构包含重复项的情况。我的意思是它实际上包含了重复的内容。异常不一定会被抛出。其他的失败场景越来越奇怪,但我不会在这里讨论
对代码的一个微不足道的修改可能涉及双重检查锁定模式的变化
public void MyMethod(string a)
{
if (!dictionary.ContainsKey(a))
{
lock (dictionary)
{
if (!dictionary.ContainsKey(a))
{
dictionary.Add(a, "someothervalue");
}
}
}
}
当然,由于我已经说过的原因,这并不安全。事实上,除了最简单的情况外(比如单例的规范实现),双重检查锁定模式在所有情况下都很难正确使用。在这个主题上有许多变化。您可以使用TryGetValue
或默认索引器进行尝试,但最终所有这些变体都是完全错误的
那么,如何在不使用锁的情况下正确地执行此操作呢?你可以试试ConcurrentDictionary。它有方法GetOrAdd
,在这些场景中非常有用。您的代码将如下所示
public void MyMethod(string a)
{
// The variable 'dictionary' is a ConcurrentDictionary.
dictionary.GetOrAdd(a, "someothervalue");
}
这就是全部。GetOrAdd
函数将检查项目是否存在。如果没有,那么它将被添加。否则,它将不使用数据结构。这一切都是以线程安全的方式完成的。在大多数情况下,ConcurrentDictionary
在不等待锁的情况下执行此操作。2
1顺便说一句,你的变量名也很讨厌。如果不是因为Servy的评论,我可能错过了一个事实:我们谈论的是词典
,而不是列表
。事实上,基于包含
调用,我首先想到的是一个列表
2在ConcurrentDictionary
上,读卡器完全无锁。但是,writers始终锁定(即添加和更新;删除操作仍然没有锁定)。这包括GetOrAdd
功能。不同之处在于数据结构维护了几个可能的锁选项,因此在大多数情况下很少或没有锁争用。这就是为什么称此数据结构为“低锁”或“并发”而不是“无锁”的原因。问题在于,如果另一个线程正在修改Collectional right,microsoft不保证list.Contains方法会起作用,公平点:这似乎不是100%安全的。在计算锁定区域中的Contains()时,另一个线程可能正在写入列表。调用字典list
实际上意味着
public void MyMethod(string a)
{
// The variable 'dictionary' is a ConcurrentDictionary.
dictionary.GetOrAdd(a, "someothervalue");
}