Планировщик в Go: Worker stealing
Другой аспект планировщика заключается в том, что это планировщик с принципом worker stealer. Это помогает в нескольких областях сохранить эффективность планирования. Во-первых, последнее, что тебе нужно, — это переход M в состояние ожидания, потому что, как только это произойдет, ОС контекстно отключит M от ядра. Это означает, что P не может выполнить какую-либо работу, даже если горутина находится в рабочем состоянии, пока контекст M не переключится обратно на ядро. Кража работы также помогает сбалансировать горутины по всем P, чтобы работа лучше распределялась и выполнялась более эффективно.
Давай рассмотрим пример.
На изображении выше у нас есть многопоточная программа Go с двумя P, обслуживающими четыре Goroutine каждый, и одной Goroutine в GRQ. Что произойдет, если один из P быстро обслужит все свои горутины?
На изображении выше у P1 больше нет горутин для выполнения. Но есть горутины в работоспособном состоянии, как в LRQ для P2, так и в GRQ. Это момент, когда P1 нужно украсть работу.
Подробнее о правилах кражи работ ты можешь узнать по ссылке.
Рассмотрим листинг №2.
Таким образом, основываясь на этих правилах в листинге 2, P1 должен проверить P2 на наличие горутин в своем LRQ и взять половину того, что он находит.
На изображении выше половина горутин берется из P2, и теперь P1 может выполнять эти горутины.
Что произойдет, если P2 закончит обслуживать все свои горутины, а у P1 ничего не останется в LRQ?
На изображении выше P2 закончил всю свою работу и теперь должен украсть часть. Во-первых, он просматривает LRQ P1, но не находит горутин. Далее он будет смотреть на GRQ. Там он найдет Горутину-9.
На изображении выше P2 крадет Горутину-9 из GRQ и начинает выполнять работу. Worker stealing хорош тем, что он позволяет системным потокам M оставаться занятыми и не простаивать. Это воровство работы рассматривается внутри компании как вращение M. У этого вращения есть и другие преимущества, о которых ты можешь узнать в блоге о краже работы.
Планировщик Go действительно удивителен тем, как его дизайн учитывает тонкости работы ОС и оборудования. Возможность превратить работу ввода-вывода/блокировки в работу, связанную с ЦП, на уровне ОС — это то, благодаря чему мы получаем большой выигрыш в использовании большей мощности ЦП с течением времени. Вот почему нам не нужно больше потоков ОС, чем у нас есть виртуальных ядер. Ты можешь разумно ожидать, что вся работа, связанная с процессором и вводом-выводом/блокировкой, будет выполнена только с одним потоком ОС на виртуальное ядро. Это возможно для сетевых приложений и других приложений, которым не нужны системные вызовы, блокирующие потоки ОС.
Разработчику, необходимо понимать, что делает приложение с точки зрения типов асинхронных задач, которые находятся в обработке. Вы не можете создать неограниченное количество горутин и ожидать потрясающей производительности. Меньшее количество горутин равно больше производительности, но с пониманием этой семантики Go-scheduler ты можешь принимать лучшие инженерные решения.
С полной версией статьи ты можешь ознакомиться по ссылке.
Рекомендуем изучить дополнительные материалы по теме: