Что происходит с потоком при блокирующем запросе к базе данных и как работает переключение между потоками
Что проверяют
Знаете ли вы, что на время ожидания ввода-вывода GIL отпускается. Отсюда растёт практический вывод: потоки помогают на запросах к базе и не помогают на счёте, и путаница здесь стоит неверно выбранной архитектуры.
Как отвечать
Поток, отправивший запрос, встаёт на системном вызове чтения из сокета. Драйвер перед этим отпускает GIL, поэтому остальные потоки в это время спокойно исполняют байт-код. Когда ответ пришёл, поток просыпается и ждёт своей очереди за блокировкой, как все.
Как выглядит переключение:
- готовые потоки распределяет планировщик операционной системы, а GIL решает, кто из них исполняет байт-код;
- на счётном коде интерпретатор просит отдать блокировку по истечении интервала переключения (
sys.setswitchinterval); - на вводе-выводе блокировка отдаётся сразу, на самом системном вызове, удерживать её незачем.
Отдельно скажите про asyncio: там синхронный драйвер блокирует не поток, а весь цикл событий, и встают все корутины разом. Лечится асинхронным драйвером либо выносом вызова в поток через asyncio.to_thread или run_in_executor.
Полезное дополнение про практику: число рабочих потоков соотносят с размером пула соединений к базе, иначе потоки просто стоят в очереди за свободным соединением, и добавление потоков ничего не ускоряет.
Где спотыкаются
Говорят «из-за GIL потоки бесполезны». Для ожидания сети и диска они полезны именно потому, что блокировка на это время отдана. Бесполезны они для счётной нагрузки, и это разные случаи, которые нужно уметь разделять вслух.
Предлагают процессы, чтобы ускорить запросы к базе. Узкое место в сети и на стороне сервера базы, а процессы добавят расход памяти и новые соединения, время ответа при этом не изменится.
Смешивают синхронный драйвер с асинхронным кодом по принципу «он же всё равно ждёт». Пока такой вызов держит цикл событий, ни один другой обработчик не выполняется, и сервис, который держал сотни соединений, начинает отвечать по одному запросу за раз.