116

From the docs:

threading.RLock() -- A factory function that returns a new reentrant lock object. A reentrant lock must be released by the thread that acquired it. Once a thread has acquired a reentrant lock, the same thread may acquire it again without blocking; the thread must release it once for each time it has acquired it.

I am not sure why do we need this? what's the difference between Rlock and Lock?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
BufBills
  • 8,005
  • 12
  • 48
  • 90

3 Answers3

173

The main difference is that a Lock can only be acquired once. It cannot be acquired again, until it is released. (After it's been released, it can be re-acaquired by any thread).

An RLock on the other hand, can be acquired multiple times, by the same thread. It needs to be released the same number of times in order to be "unlocked".

Another difference is that an acquired Lock can be released by any thread, while an acquired RLock can only be released by the thread which acquired it.


Here's an example demostrating why RLock is useful at times. Suppose you have:

def f():
  g()
  h()

def g():
  h()
  do_something1()

def h():
  do_something2()

Let's say all of f, g, and h are public (i.e. can be called directly by an external caller), and all of them require syncronization.

Using a Lock, you can do something like:

lock = Lock()

def f():
  with lock:
    _g()
    _h()

def g():
  with lock:
    _g()

def _g():
  _h()
  do_something1()

def h():
  with lock:
    _h()

def _h():
  do_something2()

Basically, since f cannot call g after acquiring the lock, it needs to call a "raw" version of g (i.e. _g). So you end up with a "synced" version and a "raw" version of each function.

Using an RLock elegantly solves the problem:

lock = RLock()

def f():
  with lock:
    g()
    h()

def g():
  with lock:
    h()
    do_something1()

def h():
  with lock:
    do_something2()
shx2
  • 61,779
  • 13
  • 130
  • 153
  • 4
    Is there ever any practical "benefit" to using `Lock`, then? – Mateen Ulhaq Nov 22 '18 at 00:24
  • 12
    @MateenUlhaq, yes. `Lock` operations are fastser. – shx2 Nov 22 '18 at 18:11
  • 4
    sorry if this is a stupid question but why would I want to acquire a lock multiple times, if I need to increment a counter I simply acquire a Lock() and release it , why do we need a counter on it to keep incrementing or decrementing – PirateApp Mar 07 '19 at 06:42
  • @PirateApp suppose you acquire a granular lock (ie: on a file) and do something fairly expensive (ie: decrypting it). an rlock prevents deadlocks from forming in a complex series of dependencies that, also, may try to acquire the same lock. – Erik Aronesty May 23 '19 at 19:06
  • @MateenUlhaq I apologize. I have read this comment "@MateenUlhaq, yes. Lock operations are fastser. – shx2 Nov 22 '18 at 18:11" and completely missed it was by shx2 not by you. My colleague used to use Lock() but I have found RLock() and is looks much better and safer. So I did wonder the same: Why would anyone use Lock()? – Valentyn Jun 22 '19 at 15:51
  • @ErikAronesty You avoid a deadlock by simply bypassing the lock? – Torsten Bronger Jan 18 '21 at 19:34
  • @TorstenBronger an rlock allows the same thread to re-enter the function, which is, often, not something you need to protect against. especially if the lock is protecting an o/s resource, or s sqlite handle, etc – Erik Aronesty Jan 21 '21 at 14:22
  • @MateenUlhaq There is a great reason actually, especially in concurrent programming, where we often have single-threaded programms running async IO. If you want the same thread to be synchronised around some IO ops, e.g. consuming from a producer, then you would use Lock and not RLock (since its the same thread and could acquire it without blocking). – Josh Albert Nov 16 '22 at 22:43
26

To expand on shx2's answer, the reason why you want to use one vs the other might be the following:

A regular Lock (mutex) is typically faster and safer.

The reason for using RLock is to avoid a dead lock due to e.g. recursion. For instance, let's put a lock in the recursive factorial function. (admittedly somewhat contrived)

from threading import Lock

lock = Lock()

def factorial(n):
    assert n > 0
    if n == 1:
        return 1
    
    with lock:       
        out = n * factorial(n - 1)

    return out

This function will cause a dead lock due to the recursive call. If we use RLock instead, however, the recursive calls can reenter the same lock as many times as needed. Hence the name reentrant (or recursive) lock.

Kris
  • 22,079
  • 3
  • 30
  • 35
-4

RLock is called a reentrant(recursive) lock. Basically it is a lock only holder can release. In Lock, any thread can release.

Alperen Sözer
  • 371
  • 3
  • 7
  • 3
    https://en.wikipedia.org/wiki/Reentrant_mutex -> In computer science, the reentrant mutex (recursive mutex, recursive lock) is a particular type of mutual exclusion (mutex) device that may be locked multiple times by the same process/thread, without causing a deadlock. – Alperen Sözer Jul 29 '21 at 13:07