Module Module 3. Advanced Concurrency & Synchronization Techniques
Objective Implement a bounded buffer (producer–consumer) using threading.Lock and threading.Condition to manage producer–consumer interactions correctly.
- Python 3 installed.
- Git installed and basic familiarity with
clone,add,commit,push. - Understanding from Modules 2 and 3:
- Threads (
threading.Thread,start(),join()). - Race conditions and critical sections.
- Mutual exclusion locks (
threading.Lock,withstatement). - Condition variables (
threading.Condition,wait(),notify(),notify_all()), and condition-checking loops. - The bounded-buffer problem (producers, consumers, capacity constraints).
- Threads (
README.mdthis filelab2_buffer.pystarter withBoundedBuffer,producer,consumer, and amainblockanalysis.mdwhere you answer the analysis questions
General instructions
- Clone the repository created for you by GitHub Classroom.
- Modify
lab2_buffer.pyto complete the tasks. - Run the script and observe producer–consumer behaviour.
- Answer the analysis questions in
analysis.md. - Commit frequently with meaningful messages.
- Push your final changes before the deadline.
The starter already validates the capacity and creates the underlying deque. Your task is to add the synchronization objects.
- Open
lab2_buffer.py. - In
BoundedBuffer.__init__:- Create one shared
threading.Lock. - Create
cv_not_fullandcv_not_emptyasthreading.Conditionobjects that both use that same lock.
- Create one shared
- Do not create separate locks for the two conditions.
The buffer intentionally uses a normal deque() rather than deque(maxlen=...). Capacity must be enforced by your synchronization logic; the container should not silently discard old items if your logic is wrong.
In BoundedBuffer.put:
- Acquire the shared lock.
- While the buffer is full, print a waiting message and wait on
cv_not_full. - Append the new item only after space is available.
- Print a produced message.
- Notify one thread waiting on
cv_not_empty.
Use a while loop for the condition check, not an if.
In BoundedBuffer.get:
- Acquire the shared lock.
- While the buffer is empty, print a waiting message and wait on
cv_not_empty. - Remove and store the oldest item with
popleft(). - Print a consumed message.
- Notify one thread waiting on
cv_not_full. - Return the item.
Use a while loop for the condition check, not an if.
- Review the provided
producer,consumer, andmain. - Run
python lab2_buffer.py. - Verify that:
- producers wait when the buffer is full;
- consumers wait when the buffer is empty;
- no item is consumed before it has been produced;
- the buffer size never exceeds
BUFFER_SIZE; - the program completes without deadlock;
- all produced items are consumed and the final buffer size is
0.
The driver distributes the total number of items exactly across the configured consumers, including when NUM_PRODUCERS * ITEMS_PER_PRODUCER is not evenly divisible by NUM_CONSUMERS.
Answer the following:
- Condition variables Why two conditions (
cv_not_full,cv_not_empty) sharing one lock are used; could one condition suffice and what are the trade-offs? wait()loops Whywait()must be in awhilethat rechecks the condition; include spurious wakeups and the fact that another thread may change the state before a woken thread proceeds.wait()behaviour What happens to the associated lock while a thread is blocked inCondition.wait(), and why is that necessary?notify()calls Consequences if producers forgetcv_not_empty.notify()or consumers forgetcv_not_full.notify().- Mutual exclusion What happens if the shared lock is not held while checking or changing the buffer state, and what does Python require when calling
Condition.wait()andCondition.notify()?
- Ensure
lab2_buffer.pyruns correctly. - Ensure
analysis.mdis complete. - Stage:
git add lab2_buffer.py analysis.md(orgit add .) - Commit:
git commit -m "Complete Lab 2 Bounded Buffer" - Push:
git push origin main(or your default branch) - Verify on GitHub that
lab2_buffer.pyandanalysis.mdare updated.