【问题标题】:Why is "lock (typeof (MyType))" a problem?为什么“锁(typeof(MyType))”有问题?
【发布时间】:2018-02-22 07:02:32
【问题描述】:

MSDN 对 C# 中的 lock 关键字给出以下警告:

一般来说,避免锁定公共 类型,或超出您的代码的实例 控制。常见的构造锁 (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反了这个 指导方针:

* lock (this) is a problem if the instance can be accessed publicly.
* lock (typeof (MyType)) is a problem if MyType is publicly accessible.

但它没有给出可靠的理由。锁(this)解释为here on SO。我对 lock(typeof(MyType)) 案例感兴趣。它有什么危险?

谢谢。

【问题讨论】:

    标签: c# multithreading locking


    【解决方案1】:

    这很危险,因为任何东西都可以占用该锁,因此很难或不可能防止死锁情况。

    曾经在 Rico Mariani 的一些 cmets 中有一篇关于此的文章(“不要锁定类型对象!”GUI 博士文章)。显然这篇文章不再直接可用,但有“镜子”在四处飘荡,包括http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects

    摘录如下:

    这里的基本问题是您不拥有类型对象,并且您不知道还有谁可以访问它。一般来说,依赖锁定一个不是您创建并且不知道还有谁可能正在访问的对象是一个非常糟糕的主意。这样做会导致僵局。最安全的方法是只锁定私有对象。

    但是等等;它甚至比这更糟糕。事实证明,在当前版本的 .NET 运行时中,类型对象有时会跨应用程序域(但不是跨进程)共享。 (这通常没问题,因为它们是不可变的。)这意味着即使在不同的应用程序域(但在同一个进程中)运行的另一个应用程序也可能通过获取您想要锁定的类型对象的锁来死锁您的应用程序并且永远不会释放它。并且很容易访问该类型对象,因为该对象有一个名称——该类型的完全限定名称!请记住,lock/SyncLock 会阻塞(这是挂起的礼貌用语),直到它能够获得锁为止。依赖另一个程序或组件可以锁定并导致死锁的锁显然非常糟糕。

    【讨论】:

    • 虽然 Jon Skeet 是第一个,但您的回复提供了更多信息,并消除了我对 lock 语句工作方式的困惑,所以你得到了一颗金星......呃......我的意思是绿色复选框:) 谢谢。
    • 谢天谢地,美国差点总局和第 211、第 212 和第 213 修正案要求类似 Harrison Bergeron 的 Jon Skeet 使用糖蜜覆盖的键盘给我们其他人一个战斗的机会。
    • 好答案。我们在框架代码中使用的一个常见模式是拥有一个仅用于锁定的私有实例字段: private object thisLock = new object();对于静态锁,您可以使用私有静态字段:私有静态对象 staticLock = new object();
    【解决方案2】:

    这与 lock(this) 的问题相同 - 您锁定了其他代码可以访问的引用,因此它也可能锁定它。

    如果您有两段不相关的代码锁定在同一个引用上而没有打算相互排斥,那么在最好的情况下,由于缺乏并发性,您可能会损失一些性能 - 并且在最坏的情况下,您可能会引入死锁。

    【讨论】:

    • 嗯...我认为我对lock关键字的误解更大。从您的描述看来,如果我在程序的完全不相关的部分中有 2 个锁,它们锁在同一类型上。如果一个锁被一个线程占用,那么没有线程可以进入这个或另一个?
    • 没错。即使它们是不相关的代码,它们也使用相同的锁。这很糟糕。
    • 另一方面,有时您可能希望允许外部代码与您自己的代码同步。对于这些,最好发布锁定对象(就像所有 System.Collection.Synchronized 类型通过 SyncRoot 属性所做的那样),但如果它是唯一被您的类锁定的对象,这与 lock(这个)。只有当您不想想要与外部代码同步时,才真正可能使用私有对象进行锁定。
    • @TheDag:我会说它 is 与此不同,因为它只会由 明确 尝试共享他们知道您正在使用的锁。您可能会“巧合”锁定另一个引用,但您不会“巧合”锁定foo.SyncRoot
    • @TheDag:使用SyncRoot 允许代码处理两个或多个对象可能以不同方式包装相同底层集合的情况。例如,包装集合但提供只读访问权限的集合可能具有返回基础集合的SyncRootSyncRoot 属性。因此,如果 Proc1 在锁定其 SyncRoot 时直接访问底层集合,而 Proc2 在锁定其 SyncRoot 时访问包装器,则 Proc1 和 Proc2 的访问将是互斥的。
    【解决方案3】:

    因为typeof (MyType)(它是Type 类型的对象)的结果是可广泛访问的,并且其他线程可以锁定同一个对象,并无限期地持有该锁定。然后 MyType 的内部逻辑有效地放弃了对其同步逻辑的重要控制。如果这是有意的,这可能不是一个实际问题,但防御性/怀疑地编码应该是您的操作方式

    【讨论】:

      【解决方案4】:

      如果遵循这种修改后的并行形式的建议,这将不是“问题”:

      一般来说,避免锁定公共类型或实例不是您创建或定义的。常见的构造 lock (this)lock (typeof (MyType)) 违反此准则如果您没有创建实例或声明类型..

      但是,由于上述对于所有遇到的代码中的公共类型或可访问实例“无法保证”,MSDN 和其他来源认为对于防御性编程应该避免这些单一的潜在难以检测的运行时(死锁)问题。这是一个很好的建议,因为大多数编码员对规则不是很好或不勤奋..

      ..并且在野外遇到此类错误的人会更加坚决地表示不允许通过实施所述准则来允许此特定问题再次发生。 (带有线程 AWT UI 模型的 Java 1.0/1.1 尤其成问题。)

      lock ("mylock") 的情况非常特殊,它应该由于字符串实习而被避免,因为人们通常无法“知道”他们是否违反了上述建议..

      【讨论】:

        【解决方案5】:

        因为锁定的目标只是建立一个地方来存储锁定布尔值(我是否锁定)以供其他线程查看....

        认为锁的目标实际上以某种方式被锁定的普遍误解是错误的......“锁定”是......没有什么,除非在可以以不安全方式访问某些共享内存的方法中,您编写代码来查看这个锁,直到它被释放后才继续......使用类型对象,作为锁目标是错误的,因为代码 sn-ps 在整个解决方案进程空间中的任何位置都可以访问该类型对象和更改存储锁定布尔值的同步块。创建本地范围的对象可以更好地确保只有那些可以访问或弄乱“有风险”共享内存的线程和方法也可以访问和/或修改锁定。

        【讨论】:

          【解决方案6】:

          在“托管线程最佳实践”主题下也有说明文档。 https://msdn.microsoft.com/en-us/library/1c9txz50(v=vs.110).aspx

          它说;

          不要将类型用作锁定对象。也就是说,避免代码如 C# 中的 lock(typeof(X)) 或 Visual Basic 中的 SyncLock(GetType(X)),或者 使用 Monitor.Enter 和 Type 对象。 对于给定的类型,有 每个应用程序域只有一个 System.Type 实例。如果类型 您锁定是公开的,您自己的代码以外的代码可以锁定 就可以了,导致死锁。有关其他问题,请参阅Reliability Best Practices.

          在锁定实例时要小心,例如 C# 中的 lock(this) 或 Visual Basic 中的 SyncLock(Me)。如果您的应用程序中有其他代码, 在类型外部,锁定对象,可能会出现死锁 发生

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-12-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-27
            • 2023-04-08
            • 1970-01-01
            相关资源
            最近更新 更多