检查当前线程是不是拥有锁
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")
【讨论】:
锁定是可重入的,因此此代码不起作用。即使调用代码已经锁定,监视器也会成功锁定。以上是关于检查当前线程是不是拥有锁的主要内容,如果未能解决你的问题,请参考以下文章