Что такое локальная и глобальная очередь в планировщике Go
Что проверяют
Понимаете ли вы модель планировщика не по картинке из статьи, а настолько, чтобы объяснить, почему горутины дёшевы и откуда берётся неравномерность загрузки. Вопрос отсеивает тех, кто знает слово «горутина», но не знает, кто ей управляет.
Как отвечать
В планировщике три сущности: G это горутина, M это поток операционной системы, P это логический процессор, контекст выполнения. Число P равно GOMAXPROCS, и локальная очередь готовых горутин принадлежит именно P, а не ядру и не потоку.
Локальная очередь это кольцевой буфер фиксированного размера (двести пятьдесят шесть слотов) плюс отдельный слот runnext для только что созданной горутины. Владелец работает с ней без блокировок, поэтому постановка и снятие почти бесплатны.
Глобальная очередь одна на всю программу и защищена мьютексом. Туда попадают горутины при переполнении локальной, когда половина буфера уезжает наверх разом, а также при возврате из системного вызова, если контекст уже отобрали.
Порядок поиска работы: сначала runnext и локальная очередь; каждый шестьдесят первый оборот планировщик заглядывает в глобальную вне очереди, чтобы она не голодала; если локальная пуста, проверяются глобальная и сетевой поллер, а затем контекст крадёт половину очереди у случайного соседа.
Где спотыкаются
Привязывают локальную очередь к ядру процессора. Ядро тут ни при чём: очередь принадлежит логическому процессору, а их количество задаётся переменной окружения и с числом ядер совпадать не обязано.
Не помнят про воровство работы и объясняют балансировку одной глобальной очередью. Тогда непонятно, зачем вообще нужна локальная: весь смысл в том, что общая лежит под замком и в горячем пути её обходят стороной, забирая задачи напрямую у соседа.
Считают порядок выполнения горутин предсказуемым. Рантайм вправе украсть задачу, переставить её и вытеснить горутину на любой точке; полагаться на порядок запуска нельзя, синхронизация делается каналами и примитивами, а не надеждой на очередь.