Планировщик в Go: (а)синхронные системные вызовы
Асинхронные системные вызовы
Когда ОС, в которой ты работаешь, имеет возможность асинхронно обрабатывать системные вызовы, для более эффективной обработки системных вызовов можно использовать нечто, называемое сетевым опрашивающим устройством. Это достигается с помощью системных вызовов kqueue (MacOS), epoll (Linux) или iocp (Windows) в соответствующих ОС.
Сетевые системные вызовы могут обрабатываться асинхронно многими ОС, которые мы используем сегодня. Именно здесь сетевой опросчик получает свое название, поскольку его основное назначение — обработка сетевых операций. Используя сетевой опросчик для сетевых системных вызовов, планировщик может предотвратить блокировку горутин M при выполнении этих системных вызовов. Это помогает поддерживать доступность M для выполнения других горутин в LRQ P без необходимости создавать новую M. Это помогает снизить нагрузку на планировщик ОС.
Лучший способ увидеть, как это работает, — запустить пример.
На изображении выше показана наша базовая диаграмма планирования. Goroutine-1 выполняется на M, и есть еще три Goroutine, ожидающих в LRQ, чтобы получить свое время на M. Сетевой опрашивающий бездействует, ему нечего делать.
На изображении выше горутина-1 хочет сделать сетевой системный вызов, поэтому горутина-1 перемещается в сетевой опросчик и обрабатывается асинхронный сетевой системный вызов. Как только горутина-1 перемещена в net poller, M теперь доступен для выполнения другой горутины из LRQ. В этом случае Goroutine-2 переключается по контексту на M.
На изображении выше асинхронный сетевой системный вызов завершается сетевым опрашивающим устройством, и горутина-1 перемещается обратно в очередь LRQ для P, где она может выполниться снова. Большим преимуществом здесь является то, что для выполнения сетевых системных вызовов не требуются дополнительные M. Net poller имеет поток ОС и обрабатывается в эффективном цикле обработки событий.
Синхронные системные вызовы
Что происходит, когда Горутина хочет сделать системный вызов, который не может быть выполнен асинхронно? В этом случае сетевой опросчик не может быть использован, и горутина, выполняющая системный вызов, заблокирует M. Это прискорбно, но нет никакого способа предотвратить это. Одним из примеров системного вызова, который нельзя выполнить асинхронно, являются системные вызовы на основе файлов. Если ты используешь CGO, могут быть и другие ситуации, когда вызов функций C также блокирует M.
Примечание. В ОС Windows есть возможность асинхронного выполнения системных вызовов на основе файлов. Технически при работе в Windows можно использовать опросник сети.
Давай рассмотрим, что происходит с синхронным системным вызовом, например, файловым вводом-выводом, который приводит к блокировке M.
На изображении снова показана наша базовая диаграмма планирования, но на этот раз Горутина-1 собирается сделать синхронный системный вызов, который заблокирует M1.
На изображении выше планировщик может определить, что горутина-1 вызвала блокировку M. В этот момент планировщик отсоединяет M1 от P с все еще присоединенной блокирующей горутиной-1. Затем планировщик вводит новый M2 для обслуживания P. В этот момент можно выбрать Goroutine-2 из LRQ и переключить контекст на M2. Если M уже существует из-за предыдущего обмена, этот переход выполняется быстрее, чем создание нового M.
На изображении выше системный вызов блокировки, сделанный Goroutine-1, завершается. В этот момент Goroutine-1 может вернуться в LRQ и снова быть обслуженным P. Затем M1 откладывается в сторону для будущего использования, если этот сценарий должен повториться.