Какой паттерн использовали для решения конкуренции и синхронизации задач
Что проверяют
Понимаете ли вы разницу между потоками, процессами и asyncio и умеете ли выбрать подход под задачу. Формулировка «какой использовали» значит, что ждут пример из практики, а не список. Следом почти всегда спрашивают про GIL и про то, как вы избегали гонок и дедлоков.
Как отвечать
Опишите свою задачу и объясните выбор. Каркас:
- Producer-Consumer: частый и хороший ответ. В потоках это
queue.Queue, она уже потокобезопасна, отдельныйLockдля неё не нужен. В асинхронном кодеasyncio.Queueи ожидание черезqueue.join(); - пул воркеров:
ThreadPoolExecutorдля ввода-вывода,ProcessPoolExecutorдля вычислений, потому что GIL не даёт потокам параллельно исполнять байткод; - ограничение конкурентности:
asyncio.Semaphoreна число одновременных запросов к внешнему API,asyncio.TaskGroup(с Python 3.11) для структурной конкурентности и корректной отмены; - примитивы:
Lockдля критической секции,Eventдля сигнала,Conditionдля ожидания состояния; - распределённая синхронизация: очередь задач Celery, блокировка в Redis с TTL,
SELECT ... FOR UPDATE SKIP LOCKEDв PostgreSQL, чтобы два воркера не взяли одну задачу.
Про GIL скажите точно: он мешает параллельным вычислениям, но отпускается на время ожидания сети и диска, поэтому потоки для I/O работают. С версии 3.13 есть отдельная сборка без GIL, но стандартный интерпретатор по-прежнему с ним.
Результат приведите своими цифрами: сколько задач в секунду было до и после, как мерили. Если такого опыта не было, разберите учебный пример и честно скажите, где применили бы его в работе.
Где спотыкаются
Считают, что GIL делает код потокобезопасным. Операция counter += 1 не атомарна, и гонка на ней воспроизводится.
Вызывают блокирующий код внутри asyncio: requests или time.sleep в корутине замораживают весь цикл событий.
Берут вложенные блокировки без единого порядка захвата и получают дедлок, который проявляется только под нагрузкой.