Test a lock with out acquiring it?

2019-04-21 15:54发布

问题:

I have objects, they get locks. I want to test if they are locked without acquiring a lock. The idea is if I TryEnter() then i have to Exit() if true to only check the lock correctly.

Seems like a really basic question, how is it done?

回答1:

What possible information can you get from knowing the lock was unlocked back when you looked at it? By the time you make a decision based on that information, the lock may be already taken.



回答2:

Because the lock statement is equivalent to:

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

Can you just do this?

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

Note that I'm naming this function "ObjectWasUnlocked" as opposed to "ObjectIsUnlocked". There is no guarantee that it will still be unlocked when the function has returned.



回答3:

I was wondering the same thing while trying to audit my code for correct locking. I came up with a method using a second thread. If the lock is available to the calling thread, but unavailable to a second thread, it must be held by the first.

    /// <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"
}

I wouldn't use this in production code - there's a race condition that could blow up. However, my unit tests are mostly single threaded, so this was useful.



回答4:

Here is a related question

Checking whether the current thread owns a lock

The conclusion there was 'you can't'