How to Use threading.RLock in Python

Demonstrates threading.RLock, a reentrant lock that allows the same thread to acquire it multiple times without deadlocking — essential for recursive functions sharing state across threads.

Medium Python 3.9+ Aug 9, 2026 Concurrency & performance 14 views 0 copies

Python code

24 lines
Python 3.9+
import threading
import time

lock = threading.RLock()
shared_counter = 0

def recursive_increment(value, depth):
    global shared_counter
    with lock:
        shared_counter += 1
        print(f"Depth {depth}: counter = {shared_counter}")
        if depth > 1:
            recursive_increment(value, depth - 1)

def worker(value):
    recursive_increment(value, 3)

if __name__ == "__main__":
    threads = [threading.Thread(target=worker, args=(i,)) for i in range(2)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(f"Final counter: {shared_counter}")

Output

stdout
Depth 3: counter = 1
Depth 2: counter = 2
Depth 1: counter = 3
Depth 3: counter = 4
Depth 2: counter = 5
Depth 1: counter = 6
Final counter: 6

How it works

threading.RLock (reentrant lock) tracks ownership per thread and a recursion count, so the same thread can acquire it multiple times safely. This is crucial for recursive functions, since a plain threading.Lock would deadlock the moment the function tries to acquire it again. The with lock: statement ensures every acquire is matched by a release, even when exceptions propagate. Each thread runs recursive_increment to depth 3, and the counter increments in a thread-safe way thanks to the lock. The final value reflects the total number of increments across all threads.

Common mistakes

  • Using a plain Lock instead of RLock, which causes a deadlock in recursive code
  • Forgetting to join threads before reading the final counter, leading to a race condition
  • Acquiring the RLock without a `with` block and failing to release it on early returns

Variations

  1. Use `lock.acquire()` / `lock.release()` explicitly for finer control in non-blocking scenarios
  2. Replace recursion with an iterative loop and a single `with lock:` block to avoid needing RLock at all

Real-world use cases

  • Guarding shared state inside a recursive tree or graph traversal that runs across multiple threads.
  • Protecting a nested lock acquisition pattern, like a cache layer calling a locked database function.
  • Updating a shared counter or accumulator from recursive worker threads in a parallel processing pipeline.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Concurrency & performance

Related tutorials and quizzes for this topic.