是锁定实例还是基于成员

Is locking instance or member based

我有一个关于在 C# 中锁定的问题。 c# 是否锁定对象的实例或成员。

如果我有以下代码:

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

即使我将 testVar 设置为新值,c# 是否保持锁定?

所有 C# 对象都继承自 System.Object,它本身始终包含 4 个字节,专供您使用 lock 的语法糖时使用。这称为 SyncBlock 对象

当您使用 new 创建新对象时,在您的例子中,ToList 生成了对 List<T> 的新引用,您实际上是 覆盖 旧引用,这会使您的 lock 无效。这意味着现在多个线程可以 可能 在您的 lock 中。编译器会将您的代码转换为带有额外局部变量的 try-finally 块,以避免您开枪。

这就是为什么最好的做法是定义一个 专用的私有只读变量 来充当同步根对象,而不是使用 class 成员。这样,阅读您代码的任何人都清楚您的意图。

编辑:

有一个nice article on MSDN描述内存中的对象结构:

SyncTableEntry also stores a pointer to SyncBlock that contains useful information, but is rarely needed by all instances of an object. This information includes the object's lock, its hash code, any thunking data, and its AppDomain index. For most object instances, there will be no storage allocated for the actual SyncBlock and the syncblk number will be zero. This will change when the execution thread hits statements like lock(obj) or obj.GetHashCode.

它锁定表达式 (testVar) 解析到的对象。这意味着您的代码确实存在线程竞争,因为一旦重新分配列表,其他并发线程可能会锁定 new 实例。

一个好的经验法则:只在 readonly 字段上 locktestVar 显然不是...但它 可能是 ,尤其是当您使用 RemoveAll 更改现有列表而不是创建新列表时。这当然取决于 alllock.

中发生的列表的访问

但坦率地说,大多数代码不需要是线程安全的。如果代码需要线程安全,支持的使用场景必须让实现者清楚理解。

lock 表达式使用 Monitor.Enter/Monitor.Exit 转换为 try/finally 表达式。 使用一些类似于您的代码(使用 VS2015 预览版)进行简单测试,您可以看到编译器将代码翻译成什么。

密码

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个变量,分别是listlist2。然后会发生以下情况:

  1. list2 被初始化为 list,现在两个引用都指向 List<int> 的同一个实例。
  2. 调用Monitor.Enter(list2, ref lockTaken)List<int>对象中的同步块与当前线程相关联。
  3. list 变量被分配给 List<int> 的新实例,但 list2 仍然指向我们锁定的原始实例。
  4. 正在使用 list2
  5. 释放锁

因此,即使您认为您正在更改锁变量,但实际上并没有。然而,这样做会使您的代码难以阅读和混淆,因此您应该按照其他帖子的建议使用专用的锁变量。