Как реализовать схему производитель — потребитель?
- Что такое блокирующая очередь?
- Как передавать задачи между потоками?
- Что делать, если производитель быстрее потребителя?
Производители кладут задачи в ограниченную блокирующую очередь, потребители забирают. Полная очередь останавливает производителя, пустая — потребителя.
- Готовые: BlockingQueue в Java, queue.Queue и asyncio.Queue в Python, каналы в Go
- Своя реализация: мьютекс + две условные переменные «не пусто» и «не полно»
- Ограниченная ёмкость — это backpressure: быстрый производитель не съест память
А ещё Остановка: «ядовитая пилюля» или закрытие канала
Подробно
Схема развязывает тех, кто создаёт работу, и тех, кто её выполняет: их можно масштабировать отдельно, а очередь сглаживает всплески.
Готовые очереди:
- Java:
ArrayBlockingQueue(ограниченная, на массиве),LinkedBlockingQueue(по умолчанию неограниченная — задайте ёмкость),SynchronousQueue(без буфера — передача из рук в руки); методыput/takeблокируют,offer/pollс таймаутом; - Python:
queue.Queue(maxsize=...)для потоков,asyncio.Queueдля корутин; - Go: буферизованный канал
make(chan Task, 100).
Реализация на мониторе — классическое упражнение на собеседовании: мьютекс и две условные переменные. put ждёт, пока очередь полна, кладёт и будит потребителей; take ждёт, пока пусто, забирает и будит производителей. Ждать — в цикле while, а не if: при пробуждении условие мог перехватить другой поток.
Ограниченная ёмкость. Если производитель быстрее, неограниченная очередь растёт до исчерпания памяти, а каждая задача ждёт всё дольше. Ограничение заставляет производителя ждать или получить отказ — это и есть backpressure: замедление распространяется к источнику. Альтернативы — отбрасывать старые или новые элементы (для метрик и телеметрии), масштабировать потребителей.
Остановка. Потребители висят на take. Варианты: положить в очередь специальный маркер — «ядовитую пилюлю» (по одной на потребителя); прерывание потоков; закрытие канала в Go (range по каналу завершится).
Масштаб выше. Та же схема между сервисами — брокер сообщений (Kafka, RabbitMQ): очередь переживает перезапуски, потребителей можно добавлять, но появляются вопросы гарантий доставки и идемпотентности.
Пример
import threading
from collections import deque
class BoundedQueue:
def __init__(self, capacity):
self.items, self.capacity = deque(), capacity
lock = threading.Lock()
self.not_empty = threading.Condition(lock)
self.not_full = threading.Condition(lock)
def put(self, item):
with self.not_full:
while len(self.items) >= self.capacity:
self.not_full.wait()
self.items.append(item)
self.not_empty.notify()
def take(self):
with self.not_empty:
while not self.items:
self.not_empty.wait()
item = self.items.popleft()
self.not_full.notify()
return itemЧто хотят услышать
- Ограниченная блокирующая очередь как основа.
- Реализация на мьютексе и двух условиях, ожидание в цикле.
- Зачем ограничивать ёмкость — backpressure.
- Корректная остановка потребителей.
Частые ошибки
ifвместоwhileпри ожидании условия.LinkedBlockingQueueбез ёмкости и удивление OOM.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Неограниченная очередь: производитель никогда не ждёт, а потребитель в цикле постоянно опрашивает её без блокировки, пока не появится задача
- ✓Ограниченная блокирующая очередь: полная останавливает производителя, пустая — потребителя; это backpressure
- ✗Потребитель должен постоянно опрашивать очередь в цикле без ожидания
- ✗Производитель и потребитель обязаны работать в одном потоке
Что спросят дальше
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.