Sharing Data Safely
Use queues and locks.
Sharing Data Safely is a free Python Academy lesson on CoddyKit. This is lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Python Academy learning path, and your progress syncs across the web and the CoddyKit app. The Python Academy course includes 4 lessons in total.
The Race Condition Problem
When multiple threads modify the same data, their operations can interleave and corrupt the result. This is a race condition. We need synchronization tools to share data safely.
import threading
counter = 0
def bump():
global counter
for _ in range(100000):
counter += 1
ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter (may be < 200000):', counter)Lock to the Rescue
A threading.Lock ensures only one thread enters a critical section at a time. Acquire it with a with block around the shared update.
import threading
counter = 0
lock = threading.Lock()
def bump():
global counter
for _ in range(100000):
with lock:
counter += 1
ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter:', counter)acquire and release
A lock can be used manually with acquire() and release(), but the with form is safer because it releases even if an error occurs.
import threading
lock = threading.Lock()
lock.acquire()
try:
print('inside critical section')
finally:
lock.release()
print('released')Thread-Safe Queues
The queue.Queue class is already thread-safe. Threads can put and get without explicit locks, making it the preferred way to pass data.
import queue, threading
q = queue.Queue()
def producer():
for i in range(3):
q.put(i)
t = threading.Thread(target=producer)
t.start()
t.join()
while not q.empty():
print('got', q.get())Producer and Consumer
The classic pattern: producers put work into a queue, consumers get and process it. The queue handles all the locking.
import queue, threading
q = queue.Queue()
results = []
def consumer():
while True:
item = q.get()
if item is None:
break
results.append(item * 10)
q.task_done()
t = threading.Thread(target=consumer)
t.start()
for i in range(3):
q.put(i)
q.put(None)
t.join()
print(results)join and task_done
q.join() blocks until every item that was put has had a matching task_done(). This lets the main thread wait for all work to finish.
import queue, threading
q = queue.Queue()
def worker():
while True:
item = q.get()
q.task_done()
threading.Thread(target=worker, daemon=True).start()
for i in range(5):
q.put(i)
q.join()
print('all items processed')RLock for Re-entrancy
A plain Lock deadlocks if the same thread tries to acquire it twice. A threading.RLock (re-entrant lock) allows that, counting acquisitions.
import threading
rlock = threading.RLock()
def outer():
with rlock:
inner()
def inner():
with rlock:
print('re-entered safely')
outer()Event for Signaling
A threading.Event lets one thread signal others. Threads call wait() until another calls set().
import threading
start = threading.Event()
def worker():
start.wait()
print('go!')
t = threading.Thread(target=worker)
t.start()
print('setting event')
start.set()
t.join()Sharing Across Processes
Across processes, use multiprocessing.Queue instead. It serializes objects between separate memory spaces.
import multiprocessing as mp
def producer(q):
for i in range(3):
q.put(i * i)
if __name__ == '__main__':
q = mp.Queue()
p = mp.Process(target=producer, args=(q,))
p.start()
p.join()
while not q.empty():
print('received', q.get())Avoiding Deadlock
Deadlock happens when threads wait on each other's locks forever. Avoid it by always acquiring multiple locks in the same order everywhere.
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def safe():
with lock_a:
with lock_b:
print('acquired in consistent order')
safe()Prefer Queues Over Locks
When possible, prefer message passing with queues over shared mutable state guarded by locks. It is easier to reason about and far less bug-prone.
import queue
q = queue.Queue()
for word in ['safe', 'simple', 'clear']:
q.put(word)
while not q.empty():
print(q.get())Quick Check
Test your understanding of safe data sharing.
Recap
You learned to share data safely:
- Race conditions corrupt unguarded shared state.
LockandRLockprotect critical sections.queue.Queueis thread-safe; usemultiprocessing.Queueacross processes.Eventsignals between threads; consistent lock order avoids deadlock.
Next course: running external programs with subprocess.
Learn Python with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 76
- Lessons
- 320
Frequently Asked Questions
Is the “Sharing Data Safely” lesson free?
Yes — the full text of “Sharing Data Safely” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Python Academy course, upgrade to CoddyKit PRO. The Python Academy course includes 4 lessons in total.
What will I learn in “Sharing Data Safely”?
Use queues and locks. You practise Python Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Python Academy?
No prior experience is required. Python Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.
How long does the “Sharing Data Safely” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Python Academy lesson?
Yes. Every Python Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Threads and the GIL
- ThreadPoolExecutor
- multiprocessing Basics
- Sharing Data Safely