Network poller
Для контраста Миша измерил сетевую часть: тысячи клиентов висели на загрузках, а потоков было всего ничего. Аня показала, как сетевое ожидание паркует горутину и сразу освобождает поток.
сетевой I/O не блокирует поток: горутина паркуется, её дескриптор регистрируется в network poller, и M освобождается немедленно — поэтому миллион соединений не стоит миллиона потоков.
Чтение из сокета
Когда conn.Read понимает, что данных в буфере ядра нет, начинается ожидание. Сокет работает в неблокирующем режиме и зарегистрирован в poller с интересом «сообщи, когда можно читать», так что горутине остаётся только уснуть: она вызывает gopark, сохраняет состояние и выбывает из планирования. Поток M, на котором она бежала, тут же освобождается и берёт следующую горутину.
Под капотом — epoll
Под капотом у poller'а — системный механизм мультиплексирования: epoll на Linux, kqueue на BSD/macOS, IOCP на Windows. Ядро отдаёт событие «дескриптор готов к чтению» тому, кто ждёт в epoll_wait. Poller будит горутину: переводит её в runnable и ставит в очередь планировщика. С точки зрения горутины conn.Read просто «вернулся» с данными — как будто она честно ждала. Но под капотом она не занимала ни M, ни P, пока ждала.
Почему это важно
Паркованная горутина не занимает P. Пока тысячи горутин ждут данные из своих соединений, все P свободны для тех, кто реально готов работать. Отдельного потока на соединение нет ни в одном из этих механизмов — в этом и есть ответ на вопрос, как один Go-процесс держит миллион открытых соединений.
Вы узнаете:
- как проверка событий встроена в поиск работы M
- почему события poller не лежат без дела
Проверьте себя
conn.Read ждёт данные, которых пока нет в буфере ядра. Когда освобождается поток M?
Есть ли отдельный поток на каждое сетевое соединение?
Почему проверка событий poller встроена в поиск работы M?
правильных ответов
Сеть — через poller: горутина спит, поток свободен, ядро само сообщает о готовности дескриптора. Сетевые блокировки для планировщика почти бесплатны.
10 000 ₽ · 60 минут · онлайн