0Pricing
Python Academy · Lesson

Sharing Data Safely

Use queues and locks.

Sharing Data Safely is a free Python Academy lesson on CoddyKit — 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, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

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.
  • Lock and RLock protect critical sections.
  • queue.Queue is thread-safe; use multiprocessing.Queue across processes.
  • Event signals between threads; consistent lock order avoids deadlock.

Next course: running external programs with subprocess.

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, and the Python Academy course includes 4 lessons in total. 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.

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; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

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

  1. Threads and the GIL
  2. ThreadPoolExecutor
  3. multiprocessing Basics
  4. Sharing Data Safely
← Back to Python Academy