Получить доступ

Как реализовать схему производитель — потребитель?

MiddleКонкурентность
Ещё спрашивают так:
  • Что такое блокирующая очередь?
  • Как передавать задачи между потоками?
  • Что делать, если производитель быстрее потребителя?
Коротко

Производители кладут задачи в ограниченную блокирующую очередь, потребители забирают. Полная очередь останавливает производителя, пустая — потребителя.

  • Готовые: BlockingQueue в Java, queue.Queue и asyncio.Queue в Python, каналы в Go
  • Своя реализация: мьютекс + две условные переменные «не пусто» и «не полно»
  • Ограниченная ёмкость — это backpressure: быстрый производитель не съест память

А ещё Остановка: «ядовитая пилюля» или закрытие канала

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Схема развязывает тех, кто создаёт работу, и тех, кто её выполняет: их можно масштабировать отдельно, а очередь сглаживает всплески.

Готовые очереди:

  • 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.

Что спросят дальше

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем