检查当前线程是否拥有锁

时间:2009-01-30 17:22:15

标签: .net multithreading locking

假设我有以下代码:

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中删除锁定似乎很危险,因为我们稍后可能会重构代码并忘记在调用_lock之前锁定SomeHelperMethod对象。

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

private void SomeHelperMethod()
{
    Debug.Assert(Monitor.HasLock(_lock));

    //do something that requires lock on _lock
}

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

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

11 个答案:

答案 0 :(得分:12)

.NET 4.5使用Monitor.IsEntered(object)支持此功能。

答案 1 :(得分:5)

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

如果您绝对想要省略“不必要的”锁定而没有潜在重构所涉及的风险,请添加注释。如果有人更改了您的代码并且它已经中断,那么就会有一条注释可以解释原因。如果他们仍然无法弄明白,那就是他们的问题,而不是你的问题。另一种方法是创建一个类用作锁定对象,该类包含一个可以轻弹的布尔值。但是,通过这样做引入的开销(包括不是免费的try / finally块)可能不值得。

答案 2 :(得分:3)

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

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

这在设计上无法奏效。你的线程可以在if()语句和方法调用之间被抢占。允许另一个线程运行并锁定该对象。现在DoSomething()将运行,即使锁被保持。

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

您在Windows中看到完全相同的模式。除了尝试打开文件之外,无法检查文件是否被其他进程锁定。出于同样的原因。

答案 3 :(得分:2)

  

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

我发现了一种类似于Monitor.TryEnter()的解决方法,但没有可重入的问题。基本上不使用Monitor.TryEnter(),而是使用Monitor.Pulse()。如果当前线程没有锁定,则此函数抛出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

答案 4 :(得分:1)

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

亚历。

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

答案 5 :(得分:1)

请参阅我对类似问题的回答:Test a lock with out acquiring it?

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

我有类似的要求检查锁是否被当前线程锁定,所以我可以这样做。

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()方法,但几乎只是Monitor.Enter / Exit的包装器,它将IsLocked设置为true。这个例子只是说明了它在样式方面与lock(){}非常相似。

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

答案 9 :(得分:0)

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

但是,如果您需要在.NET 3.5或4下使用此功能,则可以按如下方式替换ReaderWriterLockSlim的Monitor锁的使用。

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锁编写自己的包装类。但是,其他帖子提到了一个误导的布尔标志,因为问题不是“任何线程都持有锁?” (这是一个愚蠢而危险的问题)。正确的问题是“这个线程是否锁定了?”。没问题,只需将'flag'设为ThreadLocal计数器(用于递归锁支持)并确保在Monitor.Enter之后和Monitor.Exit之前完成更新。作为改进断言,计数器在“解锁”时不会降至0以下,因为这表示不平衡的进入/退出呼叫。

包装器的另一个原因是避免让某人在_lock.EnterReadLock调用中滑倒。根据课程的用法,这些可能实际上有帮助,但是令人惊讶的编码人员并不理解读者 - 作家锁。

答案 10 :(得分:-1)

不是最好的事情,但......

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")