Как происходит перераспределение работы между горутинами в Go
Что проверяют
Знаете ли вы про кражу работы и понимаете ли, зачем нужны локальные очереди.
Как отвечать
У каждого логического процессора есть своя локальная очередь готовых горутин, вмещающая 256 штук, и есть общая глобальная очередь. Локальные очереди нужны, чтобы не бороться за одну общую структуру: без них каждое переключение требовало бы синхронизации между всеми ядрами.
Когда логический процессор освобождается, он ищет работу в таком порядке:
- слот следующей горутины: только что созданная горутина попадает в особую ячейку и выполняется первой. Это улучшает локальность, потому что порождённая работа обычно связана с породившей;
- своя локальная очередь;
- глобальная очередь;
- опрашиватель сети: горутины, чьи операции ввода-вывода завершились;
- кража: если всё пусто, процессор выбирает случайного соседа и забирает половину его локальной очереди.
Последний пункт и есть кража работы. Смысл в том, что балансировкой занимается тот, кто простаивает, а не центральный планировщик. Ни очереди, ни диспетчера, ни блокировок на общей структуре: загруженный процессор продолжает работать, а свободный сам приходит за задачами. Забирается именно половина, чтобы кража не повторялась после каждой выполненной горутины.
Дополнительные детали, которые показывают глубину:
- глобальная очередь проверяется периодически даже при непустой локальной, примерно раз в 61 переключение. Иначе горутины из глобальной очереди могли бы голодать сколь угодно долго;
- вращение перед сном: часть потоков некоторое время активно ищет работу, прежде чем заснуть. Это компромисс между расходом процессора и задержкой реакции на появление новой горутины;
- длинные счётные горутины раньше могли не отдавать управление, потому что переключение происходило только в точках вызова функций. С версии 1.14 планировщик вытесняет их асинхронно через сигнал.
Где спотыкаются
Считают, что есть единая очередь на всю программу.
Не знают, зачем нужны локальные очереди, и не видят проблемы с блокировками.
Думают, что балансировкой занимается отдельный поток-планировщик.
Уверены, что счётный цикл без вызовов функций и сейчас может подвесить планировщик.