Планирование CPU и переключение контекста
Планировщик ОС выбирает, какая runnable-задача получит процессорное время. Переключение контекста сохраняет состояние текущей задачи и восстанавливает состояние следующей. Это необходимо для многозадачности, но не бесплатно: растут накладные расходы, теряется locality кэшей и увеличивается время ожидания в очереди.
Зачем это на интервью
Тема проверяет умение отличить CPU saturation от блокировки I/O и не лечить p99 бесконтрольным увеличением параллелизма. Для backend-сервиса важны не только средняя утилизация CPU, но и очередь runnable-задач, CPU quota контейнера, контеншен и хвост задержки.
Минимум для E4
- Объяснять состояния running, runnable и waiting/blocking.
- Понимать, что runnable-задача ждёт CPU, а waiting-задача ждёт событие, I/O или синхронизацию.
- Называть причины context switch: вытеснение, блокирующий системный вызов, ожидание lock, пробуждение более приоритетной задачи.
- Отличать увеличение throughput от простого роста числа goroutine и потоков.
На каждом логическом CPU в Linux есть очередь задач. Планировщик учитывает policy и приоритет; для обычных задач это fair scheduling, для real-time — отдельные политики. Задача может выполняться, быть готовой к выполнению или спать в ожидании. Если runnable-задач больше доступных CPU, они конкурируют за квант времени и задержка растёт даже без изменения полезной работы.
Углубление для E5/Senior
Context switch включает работу ядра с регистрами, состоянием планировщика и адресным пространством. Стоимость зависит от архитектуры и рабочей нагрузки; особенно заметен косвенный эффект — холодные L1/L2/TLB и миграция задачи между CPU. Поэтому «стоимость одного переключения» нельзя использовать как универсальную константу: измеряйте конкретную нагрузку.
В контейнере доступность CPU определяется не только числом видимых CPU. CFS quota может периодически throttle группу: приложение наблюдает паузы, хотя node не загружен на 100%. Для Go GOMAXPROCS следует сопоставлять с выделенной квотой. Излишнее число runnable goroutine повышает конкуренцию; полезнее ограничить параллелизм, размер очереди и число одновременных внешних запросов.
Старший инженер формулирует гипотезу и проверяет её: schedstat/PSI показывают давление на CPU, pidstat -w — добровольные и недобровольные переключения, perf sched и trace — задержку планирования. Результат оценивают по p95/p99, throughput, error rate и CPU throttling, а не по одной метрике context switches.
Ключевые понятия
- Runnable — задача готова выполняться, но может ждать свободный CPU; running — фактически исполняется; sleeping/waiting — ждёт события.
- Run queue — очередь runnable-задач, конкурирующих за CPU. Длинная очередь при стабильной нагрузке — сигнал дефицита CPU или избыточного параллелизма.
- Time slice — доля времени до пересмотра решения планировщика; точная длительность не должна быть частью ответа без контекста policy и версии ядра.
- Voluntary context switch происходит, когда задача сама блокируется; involuntary — когда её вытесняет планировщик.
- CPU affinity ограничивает набор CPU для задачи; migration переносит её между CPU и может ухудшить cache locality.
- CPU throttling — принудительная пауза группы при исчерпании квоты cgroup, не равная обычной высокой загрузке CPU.
Типовые вопросы
- Что происходит при context switch?
- Ядро сохраняет исполняемый контекст текущей задачи, выбирает следующую runnable-задачу и восстанавливает её состояние. Помимо прямых затрат возможны потери кэш-локальности и TLB.
- Чем runnable отличается от waiting?
- Runnable уже может работать и ждёт CPU; waiting не может продолжить до события: данных из сети, освобождения mutex, таймера или I/O. Лечение различается.
- Почему больше потоков может замедлить CPU-bound сервис?
- Потоков больше, чем CPU, создают конкуренцию, переключения и кэш-промахи. Ограничение числа активных задач часто улучшает p99 без потери throughput.
- Почему CPU usage не всегда объясняет latency в контейнере?
- Процесс может быть throttled по cgroup quota или ждать CPU на перегруженном CPU, а агрегированная метрика node/контейнера скрывает это. Нужны метрики quota/throttled time и давление CPU.
- Что означает много voluntary context switches?
- Часто это ожидание I/O, каналов, mutex или sleep. Это гипотеза, а не диагноз: надо сопоставить с профилем блокировок и трассировкой.
- Как соотносится планировщик Go с планировщиком Linux?
- Go распределяет goroutine по потокам M и P; Linux распределяет сами M среди CPU. Проблемы возможны на любом уровне, поэтому trace Go и системные метрики дополняют друг друга.
Практика
- Сравните CPU-bound программу с числом workers
1, равным числу выделенных CPU и в четыре раза больше. Соберите throughput, p99 иpidstat -w 1 -p <pid>; сформулируйте точку насыщения. - Создайте workload с mutex contention. Подтвердите ожидание через mutex profile Go и сопоставьте его с voluntary context switches, не объявляя переключения первопричиной без профиля.
- В тестовом контейнере задайте CPU quota, затем запустите постоянную нагрузку. Снимите показатели
cpu.statcgroup и покажите, как throttling меняет latency при том же коде. - Сделайте trace через
go tool traceи найдите goroutine, которая runnable дольше целевого порога. Проверьте, связан ли интервал с потоками и goroutine или с внешним ожиданием.
Частые ошибки и ловушки
- Приравнивать количество context switches к проблеме: они нормальны; важны причина, частота относительно baseline и влияние на latency.
- Оценивать CPU только средним значением, не рассматривая p99, run queue, PSI и throttled time.
- Увеличивать
GOMAXPROCSили worker pool сверх CPU quota без контрольного измерения. - Считать, что waiting-задача «нагружает CPU», либо что низкий CPU исключает дефицит CPU для конкретной очереди.
- Фиксировать affinity без причины: это может ухудшить балансировку и работу GC.
Связанные темы
Операционные системы · Процессы, потоки и goroutine · Виртуальная память · Go · Наблюдаемость
Источники
- Linux kernel documentation: Completely Fair Scheduler и документация scheduler.
- Linux kernel documentation: Control Group v2 и файл
cpu.stat. man 7 sched,man 2 sched_setaffinity,man 1 perf-sched,man 1 pidstat.- Brendan Gregg, Systems Performance, главы о CPU saturation и планировании.
- Документация Go: Execution Tracer и Package runtime.