Nearby lessons
143 of 159Python - RLock (Reentrant Lock)
- Explain the problem with a simple Lock
- Understand why recursive functions need RLock
- Know that RLock is reentrant for the owning thread
- Track the matching acquire() and release() count
- Analyze the recursive factorial example
Problem with Simple Lock
A normal Lock has an important limitation.
The standard Lock object does not care which Thread is currently holding that Lock.
If the Lock is already held and any Thread attempts to acquire the same Lock again, that Thread becomes blocked.
This rule applies even when the Thread trying to acquire the Lock again is the same Thread that already owns it.
Simple Definition:
A normal Lock cannot be acquired again by the same Thread while that Lock is already held.
Main Thread
│
▼
Acquire Lock
│
▼
Lock is Held
│
▼
Main Thread Tries
to Acquire Same Lock
Again
│
▼
Blocked
Demonstration Program - Problem with Simple Lock
Output
What Happens After This Output?
After printing:
Main Thread trying to acquire Lock Again
the program does not continue.
The Main Thread becomes blocked because it is trying to acquire the same normal Lock for the second time.
First acquire()
│
▼
Success
│
▼
Lock Already Held
by Main Thread
│
▼
Second acquire()
│
▼
Main Thread Waits
│
▼
No Thread Releases Lock
│
▼
Program Remains Blocked
Program Explanation
Step 1: Import threading
from threading import *
This imports the required classes and functions from the threading module.
Step 2: Create a Lock Object
l = Lock()
A normal Lock object is created.
Initially, this Lock is available.
Step 3: Print the First Message
print("Main Thread trying to acquire Lock")
The Main Thread displays a message before acquiring the Lock.
Step 4: Acquire the Lock
l.acquire()
The Lock is currently available.
Therefore, the Main Thread successfully acquires it.
Lock Available
│
▼
Main Thread
l.acquire()
│
▼
Lock Acquired
Step 5: Print the Second Message
print("Main Thread trying to acquire Lock Again")
This message is displayed normally.
At this point, the Main Thread still owns the Lock.
Step 6: Acquire the Same Lock Again
l.acquire()
Now the problem occurs.
The Lock is already held.
A normal Lock does not provide a reentrant facility.
Therefore, the Main Thread becomes blocked.
Why Does the Program Stop?
The Main Thread acquired the Lock once:
l.acquire()
Without releasing it, the same Main Thread again executes:
l.acquire()
Because a standard Lock is already in the locked state, the second acquire() waits for the Lock to become available.
But the same Thread is waiting and cannot continue to a future release().
Main Thread
│
▼
Acquire Lock
│
▼
Lock Held
│
▼
Acquire Again
│
▼
Wait for Lock
│
▼
Same Thread Cannot
Continue
│
▼
Blocked
Important Note - Stopping the Blocked Program
The document gives an important command-line note.
To terminate the blocking Thread from the Windows command prompt, use:
Ctrl + Break
The document specifically notes that:
Ctrl + C
does not work for this example in the described environment.
Another Problem with Simple Lock
The problem becomes more important when a Thread works with:
- Recursive functions
- Nested access to resources
In such cases, the same Thread may need to acquire the same Lock multiple times.
Thread │ ▼ Function A │ ▼ Acquire Lock │ ▼ Function B │ ▼ Needs Same Lock │ ▼ Acquire Again │ ▼ Blocked with Normal Lock
Problem with Recursive Functions
A recursive function calls itself.
Suppose a recursive function contains:
l.acquire()
Every recursive call can attempt to acquire the same Lock again.
factorial(3)
│
▼
Acquire Lock
│
▼
factorial(2)
│
▼
Acquire Same Lock Again
│
▼
Blocked with Lock
Therefore, a traditional Lock is not suitable when the same Thread must repeatedly acquire the same Lock during recursion.
Limitation of Traditional Locking
The document concludes:
Traditional Locking mechanism won't work for executing recursive functions.
The reason is simple.
Normal Lock
│
▼
First acquire()
│
▼
Success
│
▼
Same Thread Calls
acquire() Again
│
▼
Blocked
Therefore, we need a synchronization mechanism that allows the owner Thread to acquire the same Lock multiple times.
Solution - RLock
To overcome the limitation of a normal Lock, Python provides:
RLock
RLock means:
Reentrant Lock
Simple Definition:
RLock allows the owner Thread to acquire the same Lock again and again.
If another Thread tries to acquire the RLock while it is held, that other Thread must wait.
What Does Reentrant Mean?
Reentrant means that the Thread which already owns the Lock can enter the same protected locking mechanism again.
Owner Thread
│
▼
Acquire RLock
│
▼
Acquire Same RLock Again
│
▼
Allowed
│
▼
Acquire Again
│
▼
Allowed
This is the main difference between Lock and RLock.
Reentrant Facility
The reentrant facility is available only for the owner Thread.
It is not available to other Threads.
RLock
│
Owner = Thread-1
│
┌──────────┴──────────┐
│ │
▼ ▼
Thread-1 acquire() Thread-2 acquire()
Again Same RLock
│ │
▼ ▼
Allowed Wait
Therefore:
- The owner Thread can acquire the same RLock multiple times.
- Another Thread must wait until the RLock is completely released.
Creating an RLock Object
An RLock object can be created using:
l = RLock()
Complete syntax:
from threading import * l = RLock()
Here:
RLock()creates a Reentrant Lock.lrefers to that RLock object.
RLock Example
Output
Important Observation
Unlike the previous normal Lock example, the Main Thread does not become blocked on the second acquire().
This is because:
l = RLock()
is used instead of:
l = Lock()
The same owner Thread can acquire an RLock multiple times.
RLock Program Explanation
Step 1: Import threading
from threading import *
The threading functionality is imported.
Step 2: Create RLock
l = RLock()
An RLock or Reentrant Lock object is created.
Step 3: Print the First Message
print("Main Thread trying to acquire Lock")
The Main Thread announces that it is going to acquire the Lock.
Step 4: Acquire RLock
l.acquire()
The Main Thread successfully acquires the RLock.
Step 5: Print the Second Message
print("Main Thread trying to acquire Lock Again")
The Main Thread is about to acquire the same RLock again.
Step 6: Acquire RLock Again
l.acquire()
The same Main Thread already owns the RLock.
Because RLock is reentrant, the second acquisition is allowed.
Therefore, the Main Thread is not blocked.
Lock vs RLock - Same Program
Using Lock
l = Lock() l.acquire() l.acquire()
Result:
Second acquire()
│
▼
Blocked
Using RLock
l = RLock() l.acquire() l.acquire()
Result:
Second acquire()
│
▼
Allowed for
Owner Thread
Recursion Level in RLock
RLock keeps track of the recursion level.
Every time the owner Thread calls:
l.acquire()
the recursion level increases.
Every time it calls:
l.release()
the recursion level decreases.
The RLock becomes completely available to another Thread only after the required matching release() calls are executed.
Initial Level = 0
acquire()
│
▼
Level = 1
acquire()
│
▼
Level = 2
release()
│
▼
Level = 1
release()
│
▼
Level = 0
│
▼
RLock Completely Released
Matching acquire() and release() Calls
For every acquire() call, a corresponding release() call should be available.
The number of acquisitions and releases should match before the RLock becomes completely released.
Example:
l = RLock() l.acquire() l.acquire() l.release() l.release()
Here:
| Operation | Recursion Level |
|---|---|
| Initial | 0 |
First acquire() |
1 |
Second acquire() |
2 |
First release() |
1 |
Second release() |
0 |
Only after the second release() is the RLock completely released.
Important Notes About Recursion Level
- Only the owner Thread can acquire the same RLock multiple times.
- The number of
acquire()andrelease()calls should match. - RLock internally keeps track of the recursion level.
Why RLock is Useful for Recursive Functions
Consider a recursive function:
factorial(5) │ ▼ factorial(4) │ ▼ factorial(3) │ ▼ factorial(2) │ ▼ factorial(1) │ ▼ factorial(0)
The same Thread executes all these recursive calls.
If every recursive call needs the same synchronization Lock, a normal Lock creates a problem.
RLock solves this because the same owner Thread is allowed to acquire the same RLock repeatedly.
Demo Program - Synchronization Using RLock
Output
Complete Program Explanation
Step 1: Import Modules
from threading import * import time
The required threading functionality is imported.
Step 2: Create RLock
l = RLock()
A single RLock object is created.
This RLock is shared during recursive function execution.
Step 3: Define factorial()
def factorial(n):
The factorial() function calculates the factorial of a number recursively.
Step 4: Acquire RLock
l.acquire()
Every call to factorial() attempts to acquire the RLock.
Step 5: Check Base Condition
if n == 0:
result = 1
When n becomes 0, recursion stops.
The factorial of zero is:
0! = 1
Step 6: Recursive Call
result = n * factorial(n - 1)
The function calls itself.
Because the same Thread executes the recursive call, it attempts to acquire the same RLock again.
RLock permits this.
Step 7: Release RLock
l.release()
Every recursive invocation releases one acquisition of the RLock before returning.
Therefore, every acquire() has a matching release().
Step 8: Return Result
return result
The calculated factorial value is returned.
Step 9: Define results()
def results(n):
print("The Factorial of", n, "is:", factorial(n))
This function calls factorial() and displays the result.
Step 10: Create Two Threads
t1 = Thread(target=results, args=(5,)) t2 = Thread(target=results, args=(9,))
Two Threads are created.
| Thread | Function | Value |
|---|---|---|
t1 |
results() |
5 |
t2 |
results() |
9 |
Step 11: Start Both Threads
t1.start() t2.start()
Both Threads start executing.
When one Thread owns the RLock, another Thread requesting it must wait.
However, recursive calls made by the owner Thread can acquire the same RLock repeatedly.
How factorial(5) Uses RLock
Conceptually, the recursive acquisition works like this:
factorial(5)
Acquire → Level 1
│
▼
factorial(4)
Acquire → Level 2
│
▼
factorial(3)
Acquire → Level 3
│
▼
factorial(2)
Acquire → Level 4
│
▼
factorial(1)
Acquire → Level 5
│
▼
factorial(0)
Acquire → Level 6
│
▼
Base Case
result = 1
While returning:
factorial(0)
Release → Level 5
│
▼
factorial(1)
Release → Level 4
│
▼
factorial(2)
Release → Level 3
│
▼
factorial(3)
Release → Level 2
│
▼
factorial(4)
Release → Level 1
│
▼
factorial(5)
Release → Level 0
│
▼
RLock Completely Released
Factorial Calculation
For factorial(5):
factorial(5) = 5 × factorial(4) = 5 × 4 × factorial(3) = 5 × 4 × 3 × factorial(2) = 5 × 4 × 3 × 2 × factorial(1) = 5 × 4 × 3 × 2 × 1 × factorial(0) = 5 × 4 × 3 × 2 × 1 × 1 = 120
Therefore:
The Factorial of 5 is: 120
Similarly:
9! = 362880
Important Observation - What if We Use Lock?
The document gives a very important observation:
If we use a normal Lock instead of RLock in this program, the Thread will be blocked.
Suppose we change:
l = RLock()
to:
l = Lock()
Then:
factorial(5)
│
▼
Acquire Lock
│
▼
factorial(4)
│
▼
Same Thread Attempts
to Acquire Same Lock
│
▼
Blocked
This demonstrates why RLock is required for this recursive program.
Execution Flow - RLock Factorial Program
Program Starts
│
▼
Create RLock
│
▼
Create Threads
t1 and t2
│
▼
Start Threads
│
▼
One Thread Acquires
RLock
│
▼
factorial(n)
│
▼
Recursive Function Calls
│
▼
Same Owner Thread
Acquires RLock Again
│
▼
Recursion Continues
│
▼
Base Condition
n == 0
│
▼
Recursive Calls Return
│
▼
Matching release()
Calls Execute
│
▼
Recursion Level
Becomes 0
│
▼
RLock Completely Released
│
▼
Waiting Thread Can
Acquire RLock
│
▼
All Threads Complete
│
▼
Program Ends
Problem with Lock vs Solution with RLock
| Situation | Lock | RLock |
|---|---|---|
| First acquisition by Thread | Allowed | Allowed |
| Same owner Thread acquires again | Blocked | Allowed |
| Different Thread tries while held | Blocked | Blocked |
| Recursive function | Not suitable | Suitable |
| Nested resource access | Not suitable | Suitable |
| Tracks owner Thread | No | Yes |
| Tracks recursion level | No | Yes |
Difference Between Lock and RLock
| Lock | RLock |
|---|---|
| A Lock object can be acquired by only one Thread at a time. Even the owner Thread cannot acquire the same Lock multiple times. | An RLock object can be acquired by only one Thread at a time, but the owner Thread can acquire the same RLock multiple times. |
| Not suitable for recursive functions and nested access calls. | Best suitable for recursive functions and nested access calls. |
| Lock takes care of whether it is locked or unlocked. | RLock takes care of whether it is locked or unlocked and also maintains owner Thread information and recursion level. |
Lock and RLock Visual Comparison
Normal Lock
Thread-1 │ ▼ acquire() │ ▼ Success │ ▼ acquire() Again │ ▼ BLOCKED
RLock
Thread-1 │ ▼ acquire() │ ▼ Success Level = 1 │ ▼ acquire() Again │ ▼ Success Level = 2 │ ▼ release() Level = 1 │ ▼ release() Level = 0 │ ▼ RLock Released
When Should We Use RLock?
RLock is particularly useful when:
- A recursive function requires synchronization.
- A Thread may enter the same protected code multiple times.
- Functions call other synchronized functions that use the same Lock.
- Nested resource access requires repeated acquisition by the same Thread.
Recursive / Nested Access
│
▼
Same Thread May Need
Same Lock Again
│
▼
Use RLock
Summary
- A normal
Lockcannot be acquired repeatedly by the same owner Thread while it remains locked. - If the owner Thread attempts to acquire the normal Lock again, it becomes blocked.
- This creates problems with recursive functions and nested resource access.
- Traditional Locking is therefore not suitable for such recursive locking requirements.
- Python provides
RLockto solve this problem. RLockstands for Reentrant Lock.- The owner Thread can acquire the same RLock multiple times.
- Other Threads must wait while the RLock is held.
- RLock maintains the recursion level.
- Every
acquire()must have a correspondingrelease(). - The RLock becomes completely released when the recursion level returns to zero.
- RLock is suitable for recursive functions and nested resource access.
Important Notes
- A standard Lock allows only one acquisition while it remains locked.
- Even the owner Thread cannot acquire the same normal Lock again.
- Acquiring the same normal Lock twice causes the Thread to become blocked.
- The document notes using
Ctrl + Breakto terminate the blocked Thread from the Windows command prompt in its example environment. - Recursive functions may attempt to acquire the same Lock multiple times.
- Nested resource access can also require repeated acquisition of the same Lock.
- Traditional Lock is not suitable for such cases.
RLockmeans Reentrant Lock.- The owner Thread can acquire the same RLock multiple times.
- The reentrant facility is available only to the owner Thread.
- Other Threads remain blocked while another Thread owns the RLock.
- RLock keeps track of the recursion level.
- Every
acquire()call should have a matchingrelease()call. - For two
acquire()calls, two correspondingrelease()calls are required to completely release the RLock. - RLock is suitable for recursive functions.
- RLock is also suitable for nested resource access.
- In the factorial example, recursive calls made by the same Thread repeatedly acquire the same RLock.
- If a normal Lock is used instead of RLock in the factorial program, the Thread becomes blocked.
- A normal Lock blocks the owning thread if it tries to acquire again
- RLock allows the same thread to acquire it multiple times
- Every acquire() on an RLock needs a matching release()
- RLock is the right choice for recursive functions