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.
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 brevitywith 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 again5. 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()Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
