Synchronization: locks and the bounded buffer

When threads share data, the result of an operation can depend on how their steps interleave. That is a race condition. The code that touches shared data is a critical section, and mutual exclusion means only one thread at a time may be inside it. A lock (mutex) enforces this: a thread acquires the lock before the critical section and releases it afterwards.

Python
import queue
import threading

counter = 0
lock = threading.Lock()


def work():
    global counter
    for _ in range(10000):
        with lock:
            counter += 1


workers = [threading.Thread(target=work) for _ in range(4)]
for worker in workers:
    worker.start()
for worker in workers:
    worker.join()

buffer = queue.Queue(maxsize=2)
results = []


def producer():
    for item in range(1, 6):
        buffer.put(item)
    buffer.put(None)


def consumer():
    while True:
        item = buffer.get()
        if item is None:
            break
        results.append(item * item)


threads = [threading.Thread(target=producer), threading.Thread(target=consumer)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()
print(results)
print(counter)
Read the full lesson