Producer-Consumer with Condition Variables
Condition variables are a powerful synchronization primitive in concurrent programming, allowing threads to wait for specific conditions to become true. This challenge asks you to implement a classic producer-consumer problem using Python's threading module and condition variables to ensure proper synchronization and data integrity. Understanding and correctly utilizing condition variables is crucial for building robust and efficient multi-threaded applications.
Problem Description
You are tasked with implementing a producer-consumer system using threads and a shared buffer. The producer thread will generate data and add it to the buffer. The consumer thread will retrieve data from the buffer and process it. A condition variable will be used to signal when the buffer is not empty (for the consumer) and when the buffer is not full (for the producer).
What needs to be achieved:
- Create a shared buffer (a list) with a fixed capacity.
- Implement a producer thread that adds items to the buffer.
- Implement a consumer thread that removes items from the buffer.
- Use a
threading.Conditionobject to synchronize access to the buffer, preventing race conditions and ensuring that the producer doesn't add items when the buffer is full, and the consumer doesn't remove items when the buffer is empty. - The producer should generate random integers between 1 and 100 (inclusive).
- The consumer should print the item it consumes.
Key requirements:
- The buffer should have a maximum capacity.
- The producer should wait if the buffer is full.
- The consumer should wait if the buffer is empty.
- The producer and consumer threads should run concurrently.
- The program should terminate gracefully after a specified number of items have been produced and consumed.
Expected behavior:
The producer and consumer threads should operate in a synchronized manner. The consumer should only consume items when they are available in the buffer, and the producer should only produce items when there is space in the buffer. The program should terminate cleanly after all items have been produced and consumed.
Edge cases to consider:
- What happens if the producer and consumer run at significantly different speeds?
- How do you ensure that the program terminates correctly?
- What happens if the buffer capacity is very small or very large?
Examples
Example 1:
Input: buffer_capacity = 5, num_items = 10
Output: (Console output showing consumer printing integers between 1 and 100, interspersed with producer activity)
Explanation: The producer generates 10 integers and adds them to the buffer. The consumer consumes these integers one by one, printing each to the console. The condition variable ensures that the producer waits when the buffer is full and the consumer waits when the buffer is empty.
Example 2:
Input: buffer_capacity = 2, num_items = 4
Output: (Console output showing consumer printing integers between 1 and 100, interspersed with producer activity)
Explanation: Demonstrates the condition variable's role in managing a small buffer. The producer will frequently wait for space, and the consumer will frequently wait for items.
Example 3: (Edge Case)
Input: buffer_capacity = 10, num_items = 0
Output: (Program terminates without producing or consuming any items)
Explanation: Handles the case where no items need to be produced or consumed.
Constraints
1 <= buffer_capacity <= 200 <= num_items <= 100- The producer should generate random integers between 1 and 100 (inclusive).
- The program should terminate after all
num_itemshave been produced and consumed. - The code should be well-structured and readable.
Notes
- You will need to use the
threadingmodule, specificallythreading.Condition. - Consider using a
whileloop within the producer and consumer functions to continuously check the buffer's state. - The
acquire()andrelease()methods of theConditionobject are essential for protecting the shared buffer. - Use
wait()to release the lock and wait for a notification, andnotify()ornotify_all()to signal waiting threads. - Think carefully about the order in which you acquire and release the lock to avoid deadlocks.
- The random number generation can be done using the
randommodule.