简体   繁体   English

是基于锁定实例还是成员

[英]Is locking instance or member based

I have a question about locking in c#. 我有一个关于锁定c#的问题。 Does c# lock an instance of an object, or the member. c#是否锁定对象或成员的实例。

If i have the following code: 如果我有以下代码:

lock(testVar)
{
    testVar = testVar.Where(Item => Item.Value == 1).ToList();
    //... do some more stuff
}

Does c# keep the lock, even i set testVar to a new value? c#是否保持锁定,即使我将testVar设置为新值?

All C# objects inherit from System.Object , which itself always contains 4 bytes dedicated to be used when you use the syntactic sugar for lock . 所有C#对象都继承自System.Object ,它本身总是包含4个字节,专门用于在使用语法糖进行lock时使用。 That's called a SyncBlock object . 这叫做SyncBlock对象

When you create a new object using new , in your case, ToList which generated a new reference to a List<T> , you're actually overriding the old reference, which invalidates your lock . 当您使用new创建一个新对象时, ToList会生成对List<T>的新引用,您实际上会覆盖旧引用,这会使lock无效。 That means that now multiple threads could possibly be inside your lock . 这意味着现在多个线程可能在你的lock The compiler will transform your code into a try-finally block with an extra local variable, to avoid you from shooting your leg. 编译器会将您的代码转换为带有额外局部变量的try-finally块,以避免您射击腿部。

That is why the best practice is to define a dedicated private readonly variable which will act as a sync root object, instead of using a class member. 这就是为什么最佳实践是定义一个专用的私有只读变量 ,它将充当同步根对象,而不是使用类成员。 That way, your intentions are clear to anyone reading your code. 这样,任何阅读代码的人都清楚你的意图。

Edit: 编辑:

There is a nice article on MSDN which describes the objects structure in memory: MSDN上有一篇很好的文章描述了内存中的对象结构:

SyncTableEntry also stores a pointer to SyncBlock that contains useful information, but is rarely needed by all instances of an object. SyncTableEntry还存储一个指向SyncBlock的指针,该指针包含有用的信息,但很少需要对象的所有实例。 This information includes the object's lock, its hash code, any thunking data, and its AppDomain index. 此信息包括对象的锁,其哈希码,任何thunking数据及其AppDomain索引。 For most object instances, there will be no storage allocated for the actual SyncBlock and the syncblk number will be zero. 对于大多数对象实例,将不会为实际的SyncBlock分配存储空间,并且syncblk数字将为零。 This will change when the execution thread hits statements like lock(obj) or obj.GetHashCode. 当执行线程遇到诸如lock(obj)或obj.GetHashCode之类的语句时,这将改变

内存表示中的对象

It locks on the object that the expression ( testVar ) resolves to. 它锁定表达式( testVar )解析为的对象。 This means that your code does have a thread race, because once the list is reassigned, other concurrent threads could be locking on the new instance. 这意味着您的代码确实存在线程争用,因为一旦重新分配了列表,其他并发线程可能会锁定实例。

A good rule of thumb: only ever lock on a readonly field. 一个好的经验法则:只能lock readonly字段。 testVar clearly isn't... but it could be , especially if you use RemoveAll to change the existing list instead of creating a new one. testVar显然不是......但它可能是 ,特别是如果您使用RemoveAll更改现有列表而不是创建新列表。 This of course depends on all access to the list happening inside the lock . 这当然取决于对lock内发生的列表的所有访问。

Frankly, though, most code doesn't need to be thread-safe . 但坦率地说,大多数代码都不需要是线程安全的 If code does need to be thread safe, the supported use scenarios must be clearly understood by the implementer. 如果代码确实需要线程安全,那么实现者必须清楚地理解支持的使用场景。

The lock expression translates to a try/finally expression using Monitor.Enter/Monitor.Exit . lock表达式转换为使用Monitor.Enter/Monitor.Exittry/finally表达式。 Doing a simple test with some code similar to yours (with VS2015 Preview) you can see what the compiler translates the code to. 使用类似于您的代码(使用VS2015 Preview)进行简单测试,您可以看到编译器将代码转换为什么。

The code 编码

var testVar = new List<int>();
lock (testVar)
{
    testVar = new List<int>();
    testVar.Add(1);
}

Is actually translated to this: 实际上翻译成这个:

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);
    }
}

So you can see that the compiler has completely removed your variable testVar and replaced it with 2 variables, namely list and list2 . 因此,您可以看到编译器已完全删除了您的变量testVar并将其替换为2个变量,即listlist2 Then the following happens: 然后发生以下情况:

  1. list2 is initialized to list and now both references point to the same instance of List<int> . list2初始化为list ,现在两个引用都指向List<int>的同一个实例。
  2. The call Monitor.Enter(list2, ref lockTaken) associates the synchronization block in the List<int> object with the current thread. 调用Monitor.Enter(list2, ref lockTaken)List<int>对象中的同步块与当前线程相关联。
  3. The list variable is assigned to a new instance of List<int> , but list2 still points to the original instance that we locked against. list变量被分配给List<int>的新实例,但list2仍然指向我们锁定的原始实例。
  4. The lock is release using list2 锁定是使用list2释放的

So even though you think that you are changing the lock variable, you are actually not. 所以即使你认为你正在改变锁变量,你实际上并非如此。 Doing that however makes your code hard to read and confusing so you should use a dedicated lock variable as suggested by the other posts. 然而,这样做会使您的代码难以阅读和混淆,因此您应该使用其他帖子建议的专用锁定变量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM