Asyncio Lock и Semaphore: учёт результатов обработки контейнеров

Напишите решение на Python и проверьте его на странице.

Другие упражнения по этой теме: Asyncio Queue, Lock и Semaphore: задачи на очереди и воркеров.

Двухфазная приёмка контейнеров

На автоматизированный склад поступают контейнеры с грузом. Каждый контейнер состоит из набора упаковок, которые отправляются в общую очередь проверки. Пул воркеров-инспекторов разбирает эту очередь: инспекторы ограничивают число одновременных взвешиваний с помощью семафора и под защитой замка ведут учёт сводного веса каждого контейнера в общем манифесте.

Если хотя бы одна упаковка вызывает сбой сканера, бракуется весь контейнер целиком. При этом конвейер обязан довести обработку до конца: абсолютно каждая извлечённая упаковка, даже из уже забракованной партии, должна получить штатное подтверждение и быть снята с учёта очереди. Контейнер считается принятым и получает статус готовности строго тогда, когда все входящие в него упаковки успешно прошли проверку без единого сбоя.

Постройте конвейер двухфазной приёмки и сформируйте итоговый манифест, в котором статус готовности и суммарный вес зафиксированы только для полностью валидных контейнеров.

Что уже дано

При выполнении кода в окружении уже доступны структуры инспекции и вспомогательные сущности:

  • InspectionContext: объект контекста проверки. Содержит словарь containers (от 0 до 100 контейнеров, каждый содержит от 1 до 100 уникальных упаковок), словарь размеров container_sizes, заполненную очередь queue (экземпляр asyncio.Queue, содержащий объекты PackageJob(container, package)), семафор sem (asyncio.Semaphore), замок lock (asyncio.Lock), пул воркеров pool, корутину взвешивания scan(package) и изменяемое отображение manifest (context.manifest).
  • context.pool: предоставляет методы start(worker_coroutine) для регистрации и запуска воркеров в пуле и stop() для их отмены и завершения.
  • await context.scan(package): обращается к сканеру, возвращая положительный целый вес (от 1 до 1 000 000), либо возбуждает ScannerError (или его подкласс).

Вам необходимо реализовать только саму асинхронную функцию validate_containers.

Что нужно сделать

Реализуйте асинхронную функцию validate_containers(context, worker_count).

Функция принимает:

  • context — предоставленный объект InspectionContext;
  • worker_count — целое число воркеров (от 1 до 20), которых необходимо запустить через context.pool.start(...).

Функция должна вернуть объект манифеста (отображение Mapping, совместимое с context.manifest), содержащий строки для всех контейнеров партии.

Требования к обработке:

  • Воркеры должны запускаться исключительно через context.pool.start(worker_coroutine). Каждый воркер непрерывно извлекает задания из context.queue.
  • Любое обращение к сканеру await context.scan(package) должно выполняться строго под защитой семафора context.sem.
  • Повторные попытки сканирования одной и той же упаковки запрещены.
  • Если для упаковки возникло исключение ScannerError (или его подкласс), соответствующий контейнер считается забракованным. Для последующих упаковок этого же контейнера сканирование можно пропустить, однако каждая извлечённая из очереди упаковка обязана получить ровно одно подтверждение через context.queue.task_done().
  • Обновление записей в context.manifest должно происходить только тогда, когда текущая задача удерживает замок context.lock.
  • Для каждого контейнера в манифест должна быть записана пара (ready, total_weight):
  • если все упаковки контейнера успешно взвешены, статус ready равен True, а total_weight — целому суммарному весу всех его упаковок;
  • если хотя бы одна упаковка контейнера завершилась сбоем, статус ready равен False, а total_weight равен None. Промежуточный частичный вес бракованного контейнера публиковать нельзя.
  • Функция должна дождаться полной обработки очереди с помощью await context.queue.join(), остановить воркеров через await context.pool.stop() и корректно освободить все захваченные слоты семафора и замок.

Ввод и вывод

Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля функцию верхнего уровня validate_containers и вызывает её с проверочными аргументами.

Пример использования

import asyncio
async def scan(package):
    await asyncio.sleep(0)
    return {"p1": 2, "p2": 3}[package]
context = InspectionContext({"A": ["p1", "p2"]}, scan, 2)
print(asyncio.run(validate_containers(context, 2)))

Вывод:

{'A': (True, 5)}
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи