Состояния потока: выполняется (на ядре), готов (ждёт своей очереди на процессор), ждёт (ввода-вывода, блокировки, таймера). Планировщик работает с готовыми.
Вытесняющая многозадачность. Таймер периодически прерывает выполнение, и ядро решает, оставить текущий поток или переключиться. Поток также уступает процессор сам, когда блокируется. Кооперативная многозадачность, где задача отдаёт управление только сама, осталась в пользовательских планировщиках — event loop в asyncio и Node.js: одна задача с бесконечным циклом без await повесит всё.
Linux. Долгое время работал CFS — «совершенно честный планировщик»: каждому потоку учитывается виртуальное время выполнения, и следующим идёт тот, кто получил меньше всего (структура — красно-чёрное дерево). С версии 6.6 его заменил EEVDF, сохранив идею честного деления. Приоритет задаётся nice от −20 до 19 — это вес при делении, а не жёсткий порядок. Для задач реального времени есть отдельные политики (SCHED_FIFO).
CPU-bound и I/O-bound. Вычислительная задача использует весь квант, и потоков больше, чем ядер, ей не нужно. Задача ввода-вывода почти всё время ждёт, поэтому потоков может быть больше ядер — или вообще один поток с event loop. Для пулов это даёт оценку: число потоков ≈ ядра × (1 + время ожидания / время вычисления).
Load average в uptime и top — экспоненциальные средние за 1, 5 и 15 минут числа потоков, которые выполняются или готовы выполняться (в Linux ещё и ждущие диска в состоянии D). Значение 8 на 8 ядрах — полная загрузка, 30 на 8 ядрах — очередь, задержки растут. Высокий load average при низкой загрузке CPU указывает на ожидание диска.
Контейнеры. Лимит CPU в Kubernetes реализован квотой CFS: при превышении квоты потоки замораживаются до следующего периода (throttling) — отсюда скачки задержки при «свободном» процессоре.