Fundamentals 25 min read

Master Python Thread Synchronization: Locks, RLocks, Conditions, Events & Semaphores Explained

This article delves into Python's threading module, explaining thread safety, the role of various lock types—including Lock, RLock, Condition, Event, and Semaphore—along with their methods, usage patterns, common pitfalls like deadlocks, and practical code examples to illustrate proper synchronization techniques.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Master Python Thread Synchronization: Locks, RLocks, Conditions, Events & Semaphores Explained

Introduction

This article continues the discussion of the threading module with a focus on theory. Although everyday developers rarely need these details, they are essential knowledge for framework authors and are frequent interview topics.

Thread Safety

Thread safety is a concept in multithreaded or multiprocess programming where shared data is accessed by multiple threads without causing data corruption. The classic candy‑in‑a‑room analogy illustrates how unsynchronized access leads to inconsistent results.

Example: a variable num starts at 0, one thread increments it ten million times while another decrements it ten million times. The final value is unpredictable.

import threading

num = 0

def add():
    global num
    for i in range(10_000_000):
        num += 1

def sub():
    global num
    for i in range(10_000_000):
        num -= 1

if __name__ == "__main__":
    subThread01 = threading.Thread(target=add)
    subThread02 = threading.Thread(target=sub)
    subThread01.start()
    subThread02.start()
    subThread01.join()
    subThread02.join()
    print("num result : %s" % num)

The output varies (e.g., 669214, -1849179, -525674), demonstrating a race condition. Using a lock resolves the issue.

Locks

Locks are Python's mechanism for controlling thread switching order. The threading module provides five common lock types:

Lock – basic synchronization lock

RLock – recursive lock

Condition – condition lock

Event – event lock

Semaphore – semaphore lock

1. Lock() Synchronization Lock

Basic Introduction

A lock (also called a mutex) guarantees that only one thread can execute the protected code at a time.

Methods

threading.Lock() – returns a lock object

lockObject.acquire(blocking=True, timeout=1) – acquire the lock

lockObject.release() – release the lock

lockObject.locked() – returns True if the lock is held

Usage

Protecting the increment/decrement example with a lock makes the final result zero.

import threading

num = 0
lock = threading.Lock()

def add():
    lock.acquire()
    global num
    for i in range(10_000_000):
        num += 1
    lock.release()

def sub():
    lock.acquire()
    global num
    for i in range(10_000_000):
        num -= 1
    lock.release()

if __name__ == "__main__":
    lock = threading.Lock()
    subThread01 = threading.Thread(target=add)
    subThread02 = threading.Thread(target=sub)
    subThread01.start()
    subThread02.start()
    subThread01.join()
    subThread02.join()
    print("num result : %s" % num)

Deadlock

If a thread calls acquire() twice without a matching release(), it deadlocks.

import threading

num = 0

def add():
    lock.acquire()
    lock.acquire()  # deadlock
    global num
    for i in range(10_000_000):
        num += 1
    lock.release()
    lock.release()

# similar deadlocking sub() omitted for brevity

with statement

Lock objects implement the context‑manager protocol, allowing the with syntax.

import threading

num = 0
lock = threading.Lock()

def add():
    with lock:
        global num
        for i in range(10_000_000):
            num += 1

def sub():
    with lock:
        global num
        for i in range(10_000_000):
            num -= 1

if __name__ == "__main__":
    lock = threading.Lock()
    subThread01 = threading.Thread(target=add)
    subThread02 = threading.Thread(target=sub)
    subThread01.start()
    subThread02.start()
    subThread01.join()
    subThread02.join()
    print("num result : %s" % num)

2. RLock() Recursive Lock

Basic Introduction

RLock allows the same thread to acquire the lock multiple times, provided each acquire is matched by a release.

Methods

threading.RLock() – returns a recursive lock

lockObject.acquire(...)

lockObject.release()

lockObject.locked()

Usage

With an RLock the double‑acquire pattern works without deadlocking.

import threading

num = 0

def add():
    lock.acquire()
    lock.acquire()
    global num
    for i in range(10_000_000):
        num += 1
    lock.release()
    lock.release()

def sub():
    lock.acquire()
    lock.acquire()
    global num
    for i in range(10_000_000):
        num -= 1
    lock.release()
    lock.release()

if __name__ == "__main__":
    lock = threading.RLock()
    subThread01 = threading.Thread(target=add)
    subThread02 = threading.Thread(target=sub)
    subThread01.start()
    subThread02.start()
    subThread01.join()
    subThread02.join()
    print("num result : %s" % num)

with statement

RLock also supports the with syntax.

import threading

num = 0
lock = threading.RLock()

def add():
    with lock:
        global num
        for i in range(10_000_000):
            num += 1

def sub():
    with lock:
        global num
        for i in range(10_000_000):
            num -= 1

if __name__ == "__main__":
    subThread01 = threading.Thread(target=add)
    subThread02 = threading.Thread(target=sub)
    subThread01.start()
    subThread02.start()
    subThread01.join()
    subThread02.join()
    print("num result : %s" % num)

3. Condition() Condition Lock

Basic Introduction

Condition adds the ability to pause a thread and later resume it using wait() and notify() methods.

Methods

threading.Condition() – returns a condition object

lockObject.acquire(...)

lockObject.release()

lockObject.wait(timeout=None)

lockObject.wait_for(predicate, timeout=None)

lockObject.notify(n=1)

lockObject.notify_all()

Usage

Launch ten worker threads that immediately wait. The main thread then notifies a user‑specified number of threads to continue.

import threading

currentRunThreadNumber = 0
maxSubThreadNumber = 10

def task():
    global currentRunThreadNumber
    thName = threading.current_thread().name
    condLock.acquire()
    print("start and wait run thread : %s" % thName)
    condLock.wait()
    currentRunThreadNumber += 1
    print("carry on run thread : %s" % thName)
    condLock.release()

if __name__ == "__main__":
    condLock = threading.Condition()
    for i in range(maxSubThreadNumber):
        threading.Thread(target=task).start()
    while currentRunThreadNumber < maxSubThreadNumber:
        notifyNumber = int(input("Please enter the number of threads that need to be notified to run:"))
        condLock.acquire()
        condLock.notify(notifyNumber)
        condLock.release()
    print("main thread run end")

with statement

Condition also supports the with syntax for automatic acquire/release.

import threading

currentRunThreadNumber = 0
maxSubThreadNumber = 10

def task():
    global currentRunThreadNumber
    thName = threading.current_thread().name
    with condLock:
        print("start and wait run thread : %s" % thName)
        condLock.wait()
        currentRunThreadNumber += 1
        print("carry on run thread : %s" % thName)

if __name__ == "__main__":
    condLock = threading.Condition()
    for i in range(maxSubThreadNumber):
        threading.Thread(target=task).start()
    while currentRunThreadNumber < maxSubThreadNumber:
        notifyNumber = int(input("Please enter the number of threads that need to be notified to run:"))
        with condLock:
            condLock.notify(notifyNumber)
    print("main thread run end")

4. Event() Event Lock

Basic Introduction

Event is built on a condition lock and works like a traffic light: when cleared (red) all waiting threads block; when set (green) they all proceed.

Methods

threading.Event() – returns an event object

event.clear() – set red (pause)

event.is_set() – check state

event.set() – set green (resume)

event.wait(timeout=None) – block until green

Usage

Three threads wait for the green light, run, then wait for the next green.

import threading

maxSubThreadNumber = 3

def task():
    thName = threading.current_thread().name
    print("start and wait run thread : %s" % thName)
    eventLock.wait()
    print("green light, %s carry on run" % thName)
    print("red light, %s stop run" % thName)
    eventLock.wait()
    print("green light, %s carry on run" % thName)
    print("sub thread %s run end" % thName)

if __name__ == "__main__":
    eventLock = threading.Event()
    for i in range(maxSubThreadNumber):
        threading.Thread(target=task).start()
    eventLock.set()   # green
    eventLock.clear() # red
    eventLock.set()   # green again

5. Semaphore() Semaphore Lock

Basic Introduction

Semaphore limits the number of threads that can hold the lock simultaneously.

Methods

threading.Semaphore(value=1) – returns a semaphore object

semaLock.acquire(...)

semaLock.release()

Usage

Only two threads are allowed to run at the same time.

import threading, time

maxSubThreadNumber = 6
semaLock = threading.Semaphore(2)

def task():
    thName = threading.current_thread().name
    with semaLock:
        print("run sub thread %s" % thName)
        time.sleep(3)

if __name__ == "__main__":
    for i in range(maxSubThreadNumber):
        threading.Thread(target=task).start()

Lock Relationships

All five lock types are built on the basic synchronization lock. RLock adds a counter, Condition combines a lock and an RLock, and both Event and Semaphore are implemented internally using a Condition.

Practice Exercises

Condition lock application

Task: two threads alternately add even and odd numbers to a shared list so that the final list contains the ordered sequence 1‑100.

import threading

lst = []
condLock = threading.Condition()

def even():
    with condLock:
        for i in range(2, 101, 2):
            if len(lst) % 2 != 0:
                lst.append(i)
                condLock.notify()
                condLock.wait()
            else:
                condLock.wait()
                lst.append(i)
                condLock.notify()
        condLock.notify()

def odd():
    with condLock:
        for i in range(1, 101, 2):
            if len(lst) % 2 == 0:
                lst.append(i)
                condLock.notify()
                condLock.wait()
        condLock.notify()

if __name__ == "__main__":
    threading.Thread(target=even).start()
    threading.Thread(target=odd).start()

Event lock application

Task: two threads representing Li Bai and Du Fu converse line by line using an Event to coordinate.

import threading

def libai():
    event.wait()
    print("李白:老杜啊,不喝了我喝不下了!")
    event.set()
    event.clear()
    event.wait()
    print("李白:呼呼呼...睡着了..")

def dufu():
    print("杜甫:老李啊,来喝酒!")
    event.set()
    event.clear()
    event.wait()
    print("杜甫:老李啊,再来一壶?")
    print("杜甫:...老李?")
    event.set()

if __name__ == "__main__":
    event = threading.Event()
    t1 = threading.Thread(target=libai)
    t2 = threading.Thread(target=dufu)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

concurrencySynchronizationmultithreadingLocksthreading
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.