На техническом собеседовании Python Backend-разработчика в Wildberries кандидату задают шесть плотных вопросов: от GIL и event loop до межпроцессного обмена и Kafka. Здесь особенно хорошо видно, почему знакомого термина недостаточно. Интервьюер ждёт механизм, границы применения и небольшой рабочий пример.
Что такое GIL и как он связан с потоками?
Короткий ответ: в стандартной сборке CPython поток должен владеть Global Interpreter Lock, чтобы работать с Python-объектами. Поэтому два потока обычно не исполняют Python-байткод одновременно.
Это не означает, что многопоточность «не работает». Потоки создаёт операционная система, и она переключает их вытесняюще. Во время блокирующего I/O интерпретатор освобождает GIL, поэтому другой поток может продолжить работу. Документация Python прямо приводит чтение и запись файлов как пример такого освобождения.
Для множества синхронных сетевых вызовов потоки подходят. Для тяжёлых вычислений на Python чаще используют процессы. В актуальных версиях существуют free-threaded сборки без GIL, поэтому на собеседовании лучше уточнять реализацию и версию, а не произносить абсолютное правило.
Как event loop выполняет корутины?
Короткий ответ: цикл событий запускает одну задачу и получает управление обратно, когда та доходит до ожидания, например await сетевой операции.
Это кооперативное переключение: корутина сама должна дать другим возможность работать. В руководстве по asyncio указано, что пока Task выполняется, другая задача в том же потоке не запускается. Поэтому time.sleep(), синхронный HTTP-клиент или долгий расчёт внутри async def блокируют весь event loop.
import asyncio
async def load_user(user_id: int) -> dict:
# Асинхронное ожидание отдаёт управление другим задачам.
await asyncio.sleep(0.1)
return {"id": user_id}
async def main() -> None:
# Обе корутины ожидают I/O конкурентно в одном event loop.
users = await asyncio.gather(load_user(1), load_user(2))
print(users)
asyncio.run(main())Как процессы передают данные друг другу?
Короткий ответ: у процессов отдельные адресные пространства, поэтому обычный Python-объект не становится общим автоматически.
multiprocessing.Queue сериализует объекты через pickle и передаёт их между процессами. Manager держит объекты в отдельном серверном процессе и выдаёт proxy — это гибко, но медленнее. Value, Array и shared_memory дают общий участок памяти, для составных изменений всё равно нужна синхронизация. Эти различия подробно описаны в модуле multiprocessing.
Сильный ответ начинается с задачи: поток заданий — Queue, небольшой общий счётчик — Value с блокировкой, крупный массив без лишнего копирования — shared memory.
Чем декоратор отличается от замыкания?
Короткий ответ: декоратор принимает функцию или класс и возвращает преобразованный объект. Замыкание — функция, которая сохраняет значения из окружающей области видимости.
Декоратор часто реализуют через замыкание: внутренняя функция помнит исходную функцию и параметры. Но понятия не равны. Замыкание можно использовать без @decorator, а декоратором может быть объект класса с методом __call__. На практике важно упомянуть functools.wraps, чтобы сохранить имя, документацию и метаданные обёрнутой функции.
Почему синхронный Python-сервер перестаёт отвечать?
Короткий ответ: один долгий вызов занимает worker. Когда заняты все доступные workers, новые запросы ждут очередь.
Один блокирующий запрос не обязан остановить весь сервер: всё зависит от числа процессов или потоков и модели запуска. Для сетевого I/O подходят async-клиент или пул потоков; CPU-задачу выносят в процесс или фоновый worker; долгую бизнес-операцию защищают таймаутом и идемпотентностью. Сначала измеряют, где тратится время, и только затем меняют модель конкурентности.
Как устроены топики, партиции и offsets в Kafka?
Короткий ответ: producer пишет событие в topic, topic разбит на partitions, а offset задаёт позицию записи внутри конкретной партиции.
Порядок Kafka гарантирует внутри одной topic-partition, а не между всеми партициями. События с одинаковым ключом можно направлять в одну партицию, чтобы сохранить их последовательность. Внутри consumer group каждая партиция назначается одному consumer этой группы; другая группа читает тот же поток независимо. Базовая модель собрана во введении Apache Kafka.
Сравнивать Kafka с RabbitMQ только по удалению сообщения неверно. В Kafka записи хранятся по политике retention, а группа хранит свою позицию чтения. Для надёжной обработки дополнительно обсуждают commit offset, повторную доставку и идемпотентность consumer.
Получил ли кандидат оффер?
Факт: решение Wildberries в записи не прозвучало. Поэтому однозначное «да» или «нет» было бы выдумкой.
Наша оценка: для сильной Python Backend-роли кандидат отвечал слишком поверхностно. Он смешал кооперативность asyncio с работой потоков, не раскрыл способы межпроцессного обмена и дал неполную модель Kafka. Вероятнее всего, этого было бы недостаточно для положительного решения на данном техническом этапе.
Исправление понятное: по каждой теме собрать ответ из четырёх частей и выдержать два уточнения. Для этого можно использовать тренажёр собеседований ЮНИКОД, а затем записать собственный ответ и убрать места, где термин не подкреплён механизмом.
Источники
частые вопросы
Короткие ответы
Могут ли потоки Python выполняться параллельно?
В обычной сборке CPython один поток за раз исполняет Python-байткод под GIL. Однако потоки остаются полезны для I/O, нативный код может освобождать GIL, а free-threaded сборки CPython работают иначе.
Почему time.sleep блокирует asyncio, а asyncio.sleep — нет?
time.sleep удерживает поток event loop и не даёт ему запустить другую задачу. asyncio.sleep возвращает управление циклу, который продолжает выполнять готовые корутины.
Получил ли кандидат оффер в Wildberries?
В записи нет решения компании. По качеству технических ответов мы считаем прохождение сильного backend-этапа маловероятным, но это редакционная оценка, а не подтверждённый результат.


