4

I have a threading.Lock object and I would like to know if the current_thread is holding this lock. What would be the simplest way of achieving this?

Braiam
  • 1
  • 11
  • 47
  • 78
Rahul
  • 177
  • 1
  • 3
  • 12
  • 1
    What are you trying to accomplish? You might want to use a [reentrant lock](https://docs.python.org/2/library/threading.html#threading.RLock). See http://stackoverflow.com/questions/22885775/python-reentrant-lock-differences-with-lock for a little bit more on them. – Rob Watts May 13 '14 at 15:49
  • If it's for debugging, it's also possible to wrap the `Lock` object to keep the information. See [multithreading - Is it possible to subclass Lock() objects in Python? If not, other ways to debug deadlock? - Stack Overflow](https://stackoverflow.com/questions/6780613/is-it-possible-to-subclass-lock-objects-in-python-if-not-other-ways-to-debug?noredirect=1&lq=1) – user202729 Aug 23 '21 at 13:23

4 Answers4

7

There is no direct way to do this with threading.Lock objects that I know of. Those do have a locked attribute, but that will show up as True in all threads, not just the owning thread. It's possible with RLock, but you have to access an internal __owner attribute on the RLock object, which isn't advisable or guaranteed to always work. The code to do is would look like this, for what its worth:

#!/usr/bin/python

import threading
import time

def worker():
    if l._RLock__owner is threading.current_thread():
        print "I own the lock"
    else:
        print "I don't own the lock"
    l.acquire()
    if l._RLock__owner is threading.current_thread():
        print "Now I own the lock"
    else:
        print "Now I don't own the lock"
    time.sleep(5)
    l.release()

if __name__ == "__main__":
    l = threading.RLock()
    thds = []
    for i in range(0, 2): 
        thds.append(threading.Thread(target=worker))
        thds[i].start()

    for t in thds:
        t.join()

Here's the output:

dan@dantop:~> ./test.py
I don't own the lock
Now I own the lock
I don't own the lock

But in reality, your code really shouldn't need to do this. Why do you feel like you need this explicit check? Typically when you're writing a block of code, you know whether or not a lock has been acquired in that context. Can you share an example where you think you need to check it?

dano
  • 91,354
  • 19
  • 222
  • 219
  • **Note**: the method mentioned above with `RLock` does not work in Python 3 (in fact because it uses an undocumented feature, it may break in any new version). – user202729 Aug 23 '21 at 13:31
2

The help text for the release method:

Help on built-in function release:

release(...)
    release()

    Release the lock, allowing another thread that is blocked waiting for
    the lock to acquire the lock.  The lock must be in the locked state,
    but it needn't be locked by the same thread that unlocks it.

This implies that Lock objects don't care who locked them, as any thread can unlock them. So with unmodified lock objects, there is no way to determine if the current thread is the one holding the lock because no thread "holds" the lock - it is simply either locked or unlocked.

I assume that the reason you would want to know if you are holding the lock or not is so that you don't try to acquire the lock when you already have it. For example:

def a():
    with theLock:
        do_stuff()
        b()
        do_other_stuff()

def b():
    with theLock:
        do_b_stuff()

Here, you only want to acquire theLock in b() if the thread does not already have the lock. This, as I mentioned in my comment, is a perfect use case for a reentrant lock. If you create the lock like this:

theLock = threading.RLock()

Then the example I presented works just fine - when you call a(), it acquires the lock. Then in b(), it lets you reacquire the lock without complaining. Additionally, if you are using the context manager syntax (with theLock:) you won't need to worry about the one caveat.

The caveat is that if you are manually calling acquire and release, then you need to make sure that you call release once for every time you call acquire. In my above example, if I had called theLock.acquire() in both a() and b(), I would have needed to call release() in both functions as well. If you call acquire twice and release only once, then the lock will still be held by that thread, making other threads unable to acquire the lock.

Rob Watts
  • 6,866
  • 3
  • 39
  • 58
  • A good reason for checking this is for functions to assert that their caller has acquired a lock before calling. – Brent Sep 07 '20 at 16:47
0

Please take a look at Thread Synchronization Mechanisms

You could try the following

import threading
lock = threading.Lock()
lock.acquire()

#now  that the lock is taken, using lock.acquire(False) will return False
if not lock.acquire(False):
    # could not lock the resource

Now adding a twist to this:

import threading

class customLock(object):
    _accessLock = threading.Lock()
    def __init__(self):
        self._lock = threading.Lock()
        self.ownerThread = None

    def acquire(self, thread):
        self._accessLock.acquire()
        try:
            if self._lock.acquire():
                self.ownerThread = thread
        finally:
            self._accessLock.release()

    def release(self):
        self._accessLock.acquire()
        try:
            self._lock.release()
            self.ownerThread = None
        finally:
            self._accessLock.release()



    def getOwner(self):
        return self.ownerThread

    def acquire(self, blocking=True): 
        return self._lock.acquire(blocking)

Turning the initial example into:

import threading
lock = customLock()
lock.acquire(threading.current_thread())


#now  that the lock is taken, using lock.acquire(False) will return False
if not lock.acquire(False):
    # could not lock the resource
    if lock.getOwner() is threading.current_thread():
        # do something

Hope this helps

drapal
  • 1
  • 2
  • 1
    Hi drapal, I like this approach, the only concern I have here is that def release(self): step - 1 self._lock.release() step - 2 self.ownerThread = None the steps 1 and 2 are not atomic, wouldn't this lead to the owerThread being None even when someone has acquired the lock ? – Rahul May 13 '14 at 17:16
  • That could be a problem, I've added an internal lock to fix it. – drapal May 13 '14 at 19:56
0

In Python 3.9.5, to get the owner thread of a RLock object:

  • For debugging purpose: You can simply print the lock out, and the output would be something like

    <unlocked _thread.RLock object owner=0 count=0 at 0x7f77467d1030>
    

    Look at the owner property, which contains the thread ID of the owner thread.

  • To get the value of the owner programmatically: (warning: uses implementation details, might break in future versions;; also note that there may be a race condition between when you check the owner and actually use the information)

    It depends on whether Python is using the Python implementation, or the C implementation.

    In [12]: threading._CRLock()  # this is a recursive lock implemented in C
    Out[12]: <unlocked _thread.RLock object owner=0 count=0 at 0x7f7746878cc0>
    
    In [13]: threading._PyRLock()  # this is a recursive lock implemented in Python
    Out[13]: <unlocked threading._RLock object owner=None count=0 at 0x7f7746764cd0>
    

    The following method only works if the implementation is in Python.

    Just access the _owner attribute:

    threading._PyRLock()._owner
    
user202729
  • 3,358
  • 3
  • 25
  • 36