检查当前线程是不是拥有锁

Posted

技术标签:

【中文标题】检查当前线程是不是拥有锁【英文标题】:Checking whether the current thread owns a lock检查当前线程是否拥有锁 【发布时间】:2009-01-30 17:22:14 【问题描述】:

假设我有以下代码:

public class SomeClass()

    private readonly object _lock = new object();

    public void SomeMethodA()
    
        lock (_lock)
        
            SomeHelperMethod();

            //do something that requires lock on _lock
        
    

    public void SomeMethodB()
    
        lock (_lock)
        
            SomeHelperMethod();

            //do something that requires lock on _lock
        
    

    private void SomeHelperMethod()
    
        lock (_lock)
        
            //do something that requires lock on _lock
        
    

锁定SomeHelperMethod 内部似乎是多余和浪费的,因为所有调用者都已经获得了锁定。但是,简单地从SomeHelperMethod 中移除锁似乎很危险,因为我们可能稍后会重构代码并忘记在调用SomeHelperMethod 之前锁定_lock 对象。

理想情况下,我可以通过断言当前线程在SomeHelperMethod 内拥有_lock 上的锁来解决此问题:

private void SomeHelperMethod()

    Debug.Assert(Monitor.HasLock(_lock));

    //do something that requires lock on _lock

但似乎没有这样的方法。 Monitor.TryEnter 没有有帮助,因为锁是可重入的。因此,如果当前线程已经拥有锁,TryEnter 仍然会成功并返回true。唯一会失败的情况是另一个线程拥有锁并且调用超时。

那么,这样的方法存在吗?如果不是,为什么?这对我来说似乎一点也不危险,因为它只是告诉你当前线程(而不是另一个线程)是否拥有锁。

【问题讨论】:

【参考方案1】:

.NET 4.5 支持使用 Monitor.IsEntered(object)

【讨论】:

太棒了-谢谢!从 API 文档来看,它的添加正是我最初想要的目的:“将此方法与诊断工具一起使用,例如 Assert 方法和 Contract 类,以调试锁定问题涉及Monitor 类。"【参考方案2】:

锁定或重新锁定实际上是免费的。这种低成本的缺点是您没有其他同步机制那么多的功能。就像您在上面所说的那样,只要锁定至关重要,我就会锁定。

如果您非常想省略“不必要的”锁而不冒潜在重构的风险,请添加 cmets。如果有人更改了您的代码并且它中断了,那里有一条注释来解释原因。如果他们仍然无法弄清楚,那是他们的问题,而不是你的问题。另一种选择是创建一个用作锁定对象的类,该对象包含一个可以打开和关闭的布尔值。但是,这样做带来的开销(包括 try/finally 块,它们不是免费的)可能不值得。

【讨论】:

基本上你说的是我所怀疑的:没有办法做到这一点。虽然不是一个理想的解决方案,但这是最好的答案。 更新:但请参阅 Dave 的回答,指出现在在 .NET 4.5 中可以使用此功能 重新锁定应该几乎是免费的。锁定肯定不行。它很便宜,但不是免费的。它需要至少一个锁定部分的互锁操作和另一个解锁部分的互锁操作——假设没有争用。根据 CPU 的不同,这可能意味着大约 100 个周期,至少是旧 CPU 的 10 倍。与一个简单的函数调用的典型调用相比,这不是一个巨大的数字,但我认为不能称之为“有效免费”;)【参考方案3】:

您的请求存在根本问题。假设有一个 IsLockHeld() 方法,你会像这样使用它:

if (not IsLockHeld(someLockObj)) then DoSomething()

这不能按设计工作。您的线程可能在 if() 语句和方法调用之间被抢占。允许另一个线程运行并锁定对象。现在 DoSomething() 将运行,即使锁定已被持有。

避免这种情况的唯一方法是尝试获取锁并查看它是否有效。 Monitor.TryEnter()。

您会在 Windows 中看到完全相同的模式。除了尝试打开文件之外,没有办法检查文件是否被另一个进程锁定。出于完全相同的原因。

【讨论】:

但这不是我使用它的方式。我用它作为一个断言——作为一种知道我的代码有问题的方式。如果锁没有被持有,那就是一个错误。我不是用它来选择一个代码路径而不是另一个。 是的,你是对的。你需要一个 Monitor.Enter 的包装器,这很难看。【参考方案4】:

但似乎没有这样的方法。 Monitor.TryEnter 没有帮助,因为锁是可重入的。因此,如果当前线程已经拥有锁,TryEnter 仍然会成功并返回 true。唯一会失败的情况是另一个线程拥有锁并且调用超时。

我找到了一种类似于 Monitor.TryEnter() 的解决方法,但没有重入问题。基本上,您可以使用 Monitor.Pulse(),而不是使用 Monitor.TryEnter()。如果当前线程没有持有锁,该函数会抛出 SynchronizationLockException 异常。

示例:

try

    System.Threading.Monitor.Pulse(lockObj);

catch (System.Threading.SynchronizationLockException /*ex*/)

    // This lock is unlocked! run and hide ;)

文档:http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx

【讨论】:

不错,但如果您(或某人)实际使用Wait()/Pulse() 机制进行通信,这可能会产生副作用。【参考方案5】:

好的, 我现在明白你的问题了。 从概念上讲,我现在正在考虑一个信号量对象。您可以轻松检查信号量值(大于或等于 0 的值表示资源可用)。此外,这实际上并不会通过增加或减少信号量值来锁定正在执行的资源。 Semaphore 对象存在于 .NET 框架中,但我没有使用它,因此无法提供合适的示例。如有必要,我可以构建一个简单的示例并将其发布在这里。告诉我。

亚历克斯。

L.E. 我现在不确定您是否真的可以控制应用程序中的同步机制。如果您只能访问可锁定或不可锁定的对象,我的解决方案可能(再次)证明没有用。

【讨论】:

信号量 API 不支持这种情况。此外,这比使用简单的监视器效率要低得多。【参考方案6】:

我希望这是可能的,这将使我的代码更加简洁。

【讨论】:

【参考方案7】:

我对 .NET 编程没有任何经验,但是在我自己的代码 (Delphi) 中,我曾经通过在临界区类中使用自定义 Acquire() 和 Release() 方法来实现此类断言,并使用私有成员变量获取 cs 的线程的 id。在 EnterCriticalSection() 之后写入 GetCurrentThreadID 的返回值,在 LeaveCriticalSection() 之前重置字段值。

但我不知道 .NET 是否允许类似的事情,或者您是否可以在您的程序中实现它...

【讨论】:

【参考方案8】:

我有类似的需求来检查一个锁是否被当前线程锁定,所以我可以做这样的事情。

public void Method1()

    if(!lockHeld()) lock();
    //DO something

public void Method2()

    if(!lockHeld()) lock();
    //DO something

public void Method3()

    if(!lockHeld()) lock();

    //Do something

    unlock()

所以我为此给自己写了一个类:

public class LockObject

    private Object internalLock;

    private bool isLocked;
    public bool IsLocked
    
        get  lock (internalLock) return isLocked; 
        private set  lock (internalLock) isLocked = value; 
    

    public LockObject()
    
        internalLock = new object();
    

    public void UsingLock(Action method)
    
        try
        
            Monitor.Enter(this);
            this.IsLocked = true;

            method();
        
        finally
        
            this.IsLocked = false;
            Monitor.Exit(this);
        
    

然后我可以将它用作:

    public void Example()
    
        var obj = new LockObject();

        obj.UsingLock(() =>
        
            //Locked, and obj.IsLocked = true
        );
    

注意:我在这个类上省略了 Lock() Unlock() 方法,但几乎只是将 IsLocked 设置为 true 的 Monitor.Enter/Exit 包装器。这个例子只是说明它在风格上与 lock() 非常相似。

可能没有必要,但这对我很有用。

【讨论】:

【参考方案9】:

如果您在 .NET 4.5 下需要此功能,请参阅@Dave 接受的答案。

但是,如果您在 .NET 3.5 或 4 下需要此功能,您可以将监视器锁的使用替换为 ReaderWriterLockSlim,如下所示。

According to Joeseph Albahari 这将使您的(无争议的)锁定开销大约翻倍,但它仍然非常快(估计为 40nS)。 (免责声明,该页面没有说明测试是否使用递归锁定策略完成。)

public class SomeClass()

private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

public void SomeMethodA()

    _lock.EnterWriteLock();
    try
    
        SomeHelperMethod();

        //do something that requires lock on _lock
    
    finally
    
       _lock.ExitWriteLock();
    


private void SomeHelperMethod()

    Debug.Assert(_lock.IsWriteLockHeld);

    //do something that requires lock on _lock


如果您觉得这太不高效,您当然可以围绕 Monitor 锁编写自己的包装类。但是,其他帖子提到了一个布尔标志,该标志具有误导性,因为问题不是“任何线程都持有锁吗?” (这是一个愚蠢而危险的问题)。正确的问题是“这个线程是否持有锁?”。不过没问题,只需将“标志”设为ThreadLocal 计数器(用于递归锁支持)并确保在 Monitor.Enter 和 Monitor.Exit 之前完成更新。作为一项改进,断言计数器不会在“解锁”时降至 0 以下,因为这表明进入/退出调用不平衡。

包装器的另一个原因是避免有人滑入 _lock.EnterReadLock 调用。根据类的使用情况,这些实际上可能会有所帮助,但数量惊人的编码人员不了解读写器锁。

【讨论】:

【参考方案10】:

不是最好的办法,但是......

bool OwnsLock(object someLockObj)

  if (Monitor.TryEnter(someLockObj))
  
    Monitor.Exit();
    return false;
  
  return true;


Debug.Assert(OwnsLock(_someLockObject), "_someLockObject should be locked when this method is called")

【讨论】:

锁定是可重入的,因此此代码不起作用。即使调用代码已经锁定,监视器也会成功锁定。

以上是关于检查当前线程是不是拥有锁的主要内容,如果未能解决你的问题,请参考以下文章

并发_002 锁的概念

C#中的互斥锁是不是忙等待?

读写锁

软件构造 并发3(线程安全性)----锁定和同步

ReentrantLock源码的一点总结

死锁的概念以及处理方式