This is one example where I see the use:
Useful when
you want to have thread-safe access from outside the class and use the same methods from inside the class:
class X:
def __init__(self):
self.a = 1
self.b = 2
self.lock = threading.RLock()
def changeA(self):
with self.lock:
self.a = self.a + 1
def changeB(self):
with self.lock:
self.b = self.b + self.a
def changeAandB(self):
# you can use chanceA and changeB thread-safe!
with self.lock:
self.changeA() # a usual lock would block at here
self.changeB()
for recursion more obvious:
lock = threading.RLock()
def a(...):
with lock:
a(...) # somewhere inside
other threads have to wait until the first call of a
finishes = thread ownership.
Performance
Usually, I start programming with the Lock and when case 1 or 2 occur, I switch to an RLock. Until Python 3.2 the RLock should be a bit slower because of the additional code. It uses Lock:
Lock = _allocate_lock # line 98 threading.py
def RLock(*args, **kwargs):
return _RLock(*args, **kwargs)
class _RLock(_Verbose):
def __init__(self, verbose=None):
_Verbose.__init__(self, verbose)
self.__block = _allocate_lock()
Thread Ownership
within the given thread you can acquire a RLock
as often as you like. Other threads need to wait until this thread releases the resource again.
This is different to the Lock
which implies 'function-call ownership'(I would call it this way): Another function call has to wait until the resource is released by the last blocking function even if it is in the same thread = even if it is called by the other function.
When to use Lock instead of RLock
When you make a call to the outside of the resource which you can not control.
The code below has two variables: a and b and the RLock shall be used to make sure a == b * 2
import threading
a = 0
b = 0
lock = threading.RLock()
def changeAandB():
# this function works with an RLock and Lock
with lock:
global a, b
a += 1
b += 2
return a, b
def changeAandB2(callback):
# this function can return wrong results with RLock and can block with Lock
with lock:
global a, b
a += 1
callback() # this callback gets a wrong value when calling changeAandB2
b += 2
return a, b
In changeAandB2
the Lock would be the right choice although it does block. Or one can enhance it with errors using RLock._is_owned()
. Functions like changeAandB2
may occur when you have implemented an Observer pattern or a Publisher-Subscriber and add locking afterward.