In Part 1, you will learn how mutexes protect shared resources when multiple workers run at the same time.
You will:
- explain what can go wrong when multiple workers share one resource
- use
threading.Lockas a mutex - protect a critical section
- write a small program that safely saves generated phrases to a file
Before starting:
- In Visual Studio Code terminal, update your local repository.
git pull origin mainIf git pull fails because of local changes, use one of these paths:
Keep your work (recommended):
git stash push -m "session6-wip"
git pull origin main
git stash popDiscard your local changes in session6 only (use with care):
# Run this only from inside the session6 folder.
# Check first (it should end with `/bda/session6`):
pwd
git fetch origin
git restore --source origin/main --worktree --staged -- .- Open the
session6folder in Visual Studio Code and in terminal. - Create and activate a virtual environment.
python3 -m venv .venv
source .venv/bin/activateWindows PowerShell:
python -m venv .venv
.venv\Scripts\Activate.ps1- If
requirements.txtis missing, create it insession6/with:
requests==2.32.3
Faker
quizmd- Install dependencies:
pip install -r requirements.txt- thread: a lightweight worker that runs inside the same Python process
- shared resource: something multiple workers can access, for example a file
- race condition: a bug caused by workers interacting in an unpredictable order
- mutex: a lock that allows only one worker into a critical section
- critical section: code that must not be interrupted by another worker
A mutex is useful when many workers need to use one shared resource.
In Python, we commonly use threading.Lock():
import threading
lock = threading.Lock()
with lock:
# only one thread can run this block at a time
print("safe shared work")The with lock: block is the critical section. Other threads must wait until the lock is released.
Checkpoint question:
Why should file writing often be protected by a lock?
Show answer
Because multiple workers writing at the same time can create mixed, missing, or confusing output. A lock makes each write section finish before another worker enters it.
Before writing to a file, try a tiny example where two workers share one counter.
Create this warm-up file:
session6/solutions/exercise-06-00-lock.pyAdd this code:
from concurrent.futures import ThreadPoolExecutor
import threading
import time
counter = 0
counter_lock = threading.Lock()
def add_one(worker_name):
global counter
print(f"{worker_name} is waiting for the lock")
with counter_lock:
print(f"{worker_name} entered the critical section")
current_value = counter
time.sleep(0.5)
counter = current_value + 1
print(f"{worker_name} updated counter to {counter}")
if __name__ == "__main__":
with ThreadPoolExecutor(max_workers=2) as executor:
executor.submit(add_one, "Worker A")
executor.submit(add_one, "Worker B")
print(f"Final counter value: {counter}")Run:
python3 solutions/exercise-06-00-lock.pyWhat this shows:
- Both workers want to update the same shared variable.
- Only one worker can enter the
with counter_lock:block at a time. - The final counter value should be
2.
Show explanation
The lock protects the read-update-write sequence. Without the lock, both workers could read the same old value before either one writes the new value, which can produce an incorrect final counter.
Faker is a Python library that generates fake data for testing and practice.
In this exercise, we use it only to generate simple fake sentences. This keeps the focus on the mutex and file writing, not on building a text generator.
Try this small example first:
from faker import Faker
fake = Faker()
def generate_phrase():
return fake.sentence(nb_words=6)
print(generate_phrase())Each time you run it, you should see a short fake sentence.
Create this file and complete it:
session6/solutions/exercise-06-01.pyYour program should:
- Use
Fakerto generate simple fake sentences. - Start 10 worker threads.
- Each worker generates one phrase.
- Each worker writes its phrase to
generated_phrases.txt. - Use a mutex so only one thread writes to the file at a time.
- Print a friendly message when each phrase is saved.
Suggested skeleton:
from concurrent.futures import ThreadPoolExecutor
import threading
from faker import Faker
fake = Faker()
write_lock = threading.Lock()
def generate_phrase():
# TODO: use fake.sentence(...) to return a phrase
...
def save_phrase(index):
phrase = generate_phrase()
# TODO: use write_lock before writing to generated_phrases.txt
...
if __name__ == "__main__":
# TODO: clear generated_phrases.txt before starting
# TODO: run 10 workers with ThreadPoolExecutor
...Run:
python3 solutions/exercise-06-01.pyMinimum completion checklist:
generated_phrases.txtis created.- The file contains exactly 10 lines.
- Each line includes a phrase number and generated phrase.
- File writing is protected by
threading.Lock.
Show hint
Use fake.sentence(nb_words=6) to generate one phrase. Use with write_lock: around the file-writing block only, not around the whole phrase generation function.
Show expected shape
Your output file might look similar to this:
Phrase 1: Python analyzes logs.
Phrase 2: A worker streams records.
Phrase 3: The model summarizes results.The exact phrases may be different because Faker generates fake text.
quizmd quizzes/python-session-06-part-01-quiz.md