测试锁是否可用而不需要拥有它?

9
我有一些对象,它们被锁定了。我想在不获取锁的情况下测试它们是否被锁定。我的想法是如果我使用 TryEnter(),然后如果返回值为 true,则必须使用 Exit() 来正确检查锁。
这似乎是一个非常基本的问题,应该如何解决?
4个回答

22

当您看到锁被解锁时,您可以从中获得什么信息?但在您根据这些信息做出决定时,锁可能已经被拿走了。


3
+1,确切的。回答关于锁的过去状态的问题只会导致你做出错误的决定。 - JaredPar
3
当您检查IsBeingUsed时,线程可能已经停止了有用的工作,但在更新IsBeingUsed之前发生了上下文切换。 - Eric J.
1
@Eric:或者更糟的是:IsBeingUsed为0,而你继续进行更改,毫不知情,而在此期间,有人将其更改为1。 - Remus Rusanu
2
我可以看到这种行为的用途。想象一种情况,一个锁被长时间占用,而两个线程需要对同一组对象进行不同的操作。如果这两个集合操作不依赖于顺序,那么先查看对象并在后续处理中跳过正在使用的对象可能是有用的。 - Tullo_x86
6
@Tullo: 但这可以通过像Monitor.TryEnter一样的零等待获取更好地解决。零等待获取意味着如果锁是空闲的,您愿意抓住它,但不愿意等待。 OP要求不同的语义,即查看而不是获取。我真的看不出这种语义的任何实际用途。 - Remus Rusanu
显示剩余6条评论

3

因为lock语句等同于:

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}

你能做到这个吗?

bool ObjectWasUnlocked(object x)
{
   if(System.Threading.Monitor.TryEnter(x))
   {
       System.Threading.Monitor.Exit(x);
       return true;
   }
   else
   {
       return false;
   }
}

请注意,我将这个函数命名为“ObjectWasUnlocked”,而不是“ObjectIsUnlocked”。不能保证在函数返回时对象仍然解锁。

在这个解决方案中,虽然您获得了锁,但这是OP不想要做的事情。 - JaredPar

3

在尝试审计我的代码以确保正确的锁定时,我也在思考同样的问题。我想出了一种使用第二个线程的方法。如果调用线程可以访问锁,但第二个线程无法访问,则必须由第一个线程持有。

    /// <summary>
/// Utiltity for checking if a lock has already been acquired.
/// WARNING: This test isn't actually thread-safe, 
/// it's only really useful for unit tests
/// </summary>
private static bool ObjectIsAlreadyLockedByThisThread(object lockObject)
{
    if (!Monitor.TryEnter(lockObject))
    {
        // another thread has the lock
        return false;
    }

    Monitor.Exit(lockObject);

    bool? LockAvailable = null;

    var T = new Thread(() =>
    {
        if (Monitor.TryEnter(lockObject))
        {
            LockAvailable = true;
            Monitor.Exit(lockObject);
        }
        else
        {
            LockAvailable = false;
        }
    });

    T.Start();

    T.Join();

    return !LockAvailable.Value;
}

// Tests:
public static void TestLockedByThisThread()
{
    object MyLock = new object();
    lock (MyLock)
    {
        bool WasLocked = ObjectIsAlreadyLockedByThisThread(MyLock);

        Debug.WriteLine(WasLocked); // prints "True"
    }
}

public static void TestLockedByOtherThread()
{
    object MyLock = new object();

    var T = new Thread(() =>
    {
        lock (MyLock)
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
        }
    });

    T.Start();
    Thread.Sleep(TimeSpan.FromSeconds(1));

    bool WasLocked = ObjectIsAlreadyLockedByThisThread(MyLock);

    T.Join();

    Debug.WriteLine(WasLocked); // prints "False"
}

public static void TestNotLocked()
{
    object MyLock = new object();

    bool WasLocked = ObjectIsAlreadyLockedByThisThread(MyLock);

    Debug.WriteLine(WasLocked); // prints "False"
}

我不会在生产代码中使用这个 - 存在一种竞争条件可能导致程序崩溃。然而,我的单元测试大多是单线程的,因此这很有用。


这段代码有问题。即使无法获取锁,finally块中的第一个Monitor.Exit(lockObject)也会被调用。 - Sebastian Krysmanski
@Sebastian Krysmanski:好观点,不需要finally。已修复并清理。 - GranBurguesa

1

自4.5版本以后不再支持。 - Gaspa79

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接