我有一个关于在C#中锁定的问题。 c#是否锁定对象的实例或成员。锁定实例或成员为基础
如果我有以下代码:
lock(testVar)
{
testVar = testVar.Where(Item => Item.Value == 1).ToList();
//... do some more stuff
}
难道C#中保持锁定,即使我设置testVar
到一个新的价值?
我有一个关于在C#中锁定的问题。 c#是否锁定对象的实例或成员。锁定实例或成员为基础
如果我有以下代码:
lock(testVar)
{
testVar = testVar.Where(Item => Item.Value == 1).ToList();
//... do some more stuff
}
难道C#中保持锁定,即使我设置testVar
到一个新的价值?
所有C#对象都从System.Object
继承,它本身始终包含4个字节,专门用于为lock
使用语法糖时使用的字节。这就是所谓的SyncBlock对象。
当您使用new
,一个新的对象,你的情况,ToList
它产生一个新的参照List<T>
,你居然首要是旧的参考,你的lock
无效。这意味着现在多个线程可能可能在您的lock
内。编译器会将您的代码转换为带有额外局部变量的try-finally
块,以避免您拍摄腿部。
这就是为什么最佳实践是定义一个专用专用只读变量,它将充当同步根对象,而不是使用类成员。这样,任何读你代码的人都可以清楚你的意图。
编辑:
有一个nice article on MSDN其描述了存储器中的对象结构:
SyncTableEntry还存储一个指向同步块包含有用 信息,而是通过很少需要一个对象的所有实例。这个 信息包括对象的锁,其哈希码,任何thunking 数据,以及它的AppDomain索引。对于大多数对象实例, 将不会为实际SyncBlock分配存储空间,并且syncblk 数字将为零。当执行线程碰到 像lock(obj)或obj.GetHashCode这样的语句时,这将会改变。
它可以锁定,表达式(testVar
)解析为在物体上。这意味着你的代码确实有线程竞争,因为一旦列表被重新分配,其他并发线程就可能锁定在新的实例上。
一个很好的经验法则:只有lock
在readonly
字段。 testVar
显然不是......但它可能是,特别是如果您使用RemoveAll
来更改现有的列表,而不是创建一个新的列表。这当然取决于所有的访问lock
内发生的列表。
坦率地说,虽然大多数代码不需要线程安全。如果代码确实需要线程安全,则实现者必须清楚地理解受支持的使用场景。
的lock
表达转换为使用Monitor.Enter/Monitor.Exit
一个try/finally
表达。 使用类似于您的一些代码(使用VS2015 Preview)进行简单测试,您可以看到编译器将代码转换为的内容。
代码
var testVar = new List<int>();
lock (testVar)
{
testVar = new List<int>();
testVar.Add(1);
}
居然被翻译成这样:
List<int> list2;
List<int> list = new List<int>();
bool lockTaken = false;
try
{
list2 = list;
Monitor.Enter(list2, ref lockTaken);
list = new List<int> { 1 };
}
finally
{
if (lockTaken)
{
Monitor.Exit(list2);
}
}
所以你可以看到,编译器已经完全删除您的变量testVar
,并与2个变量,即list
和list2
取而代之。然后会发生以下情况:
list2
被初始化为list
,现在两个引用指向的List<int>
同一个实例。Monitor.Enter(list2, ref lockTaken)
在关联与当前线程的List<int>
对象同步块。list
变量被分配到的List<int>
一个新的实例,但list2
仍然指向我们锁定在原来的实例。list2
所以,即使你认为你换锁变量,你实际上没有释放。但是,这样做会使您的代码难以阅读和混淆,因此您应该使用其他帖子所建议的专用锁定变量。
为什么你需要重新分配锁? –
@VsevolodGoloviznin你对重新指定锁定有何意义?我重新分配'testVar'。 – BendEg
为什么你使用你正在使用的变量作为锁? –