【问题标题】:Is locking instance or member based是基于锁定实例还是成员
【发布时间】:2015-01-12 11:06:14
【问题描述】:

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

如果我有以下代码:

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

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

【问题讨论】:

  • 为什么需要重新分配锁?
  • @VsevolodGoloviznin 重新分配锁是什么意思?我重新分配testVar
  • 为什么要使用正在使用的变量作为锁?
  • 查看msdn关于锁的文章:msdn.microsoft.com/en-us/library/c5kehkcz.aspx
  • 你读过这个Best practice is to define a private object to lock on, or a private static object variable to protect data common to all instances.吗?

标签: c# .net multithreading locking


【解决方案1】:

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

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

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

编辑:

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

SyncTableEntry 还存储一个指向 SyncBlock 的指针,其中包含有用的 信息,但很少被对象的所有实例所需要。这 信息包括对象的锁、它的哈希码、任何 thunking 数据及其 AppDomain 索引。对于大多数对象实例,会有 没有为实际的 SyncBlock 和 syncblk 分配存储空间 数字将为零。当执行线程命中时,这将改变 像 lock(obj) 或 obj.GetHashCode 这样的语句。

【讨论】:

  • 谢谢你的图片,如果可以的话,我会再给+1。
  • @BendEg 很高兴为您提供帮助。我添加了指向 MSDN 文章的链接。您应该阅读它,有很好的内部见解。
【解决方案2】:

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

一个好的经验法则:只有lockreadonly 字段上。 testVar 显然不是......但它可能,特别是如果您使用RemoveAll 来更改现有列表而不是创建新列表。这当然取决于 alllock 内发生的列表的访问。

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

【讨论】:

    【解决方案3】:

    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个变量替换了它,即listlist2。然后发生以下情况:

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

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

    【讨论】:

      猜你喜欢
      • 2018-11-06
      • 2010-09-06
      • 2010-10-13
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多