Какие ресурсы освобождаются при вытеснении горутины в Go?
Что проверяют
Это вопрос с подвохом: хотят понять, представляете ли вы модель планировщика GMP или перечислите «стек, регистры, память». Правильный ответ начинается с того, что при вытеснении у горутины почти ничего не освобождается. Следом спрашивают, чем вытеснение отличается от блокировки и завершения.
Как отвечать
При вытеснении горутина G просто перестаёт выполняться:
- состояние: регистры и указатель стека сохраняются в структуре
g, чтобы потом продолжить с того же места; - стек: остаётся за горутиной целиком, без него продолжить выполнение нельзя;
- объекты в куче, мьютексы, файлы: остаются как были. Если горутина держала
sync.Mutex, он так и остаётся захваченным; - что освобождается: процессор P и системный поток M. Планировщик кладёт G обратно в очередь и запускает на этой паре другую горутину.
Как вытесняют: кооперативно в прологах функций через проверку стека, а с Go 1.14 ещё и асинхронно. Системный монитор sysmon замечает горутину, которая работает дольше 10 мс, и посылает её потоку сигнал SIGURG. Поэтому цикл без вызовов функций больше не может занять поток навсегда. Отключается это через GODEBUG=asyncpreemptoff=1.
Сравните с другими случаями. При блокирующем системном вызове G остаётся на своём M, а P передаётся другому потоку. При сетевом вводе-выводе горутина паркуется в netpoller и поток не занимает. Стек уменьшается не при вытеснении, а во время сборки мусора: если горутина использует меньше четверти стека, он сжимается вдвое. При завершении структура g уходит в список для переиспользования.
Где спотыкаются
Повторяют, что при вытеснении «освобождаются стек и регистры». Стек нужен для продолжения, а регистры сохраняются, а не освобождаются.
Думают, что завершение горутины закрывает файлы и отпускает мьютексы. Деструкторов в Go нет, для этого пишут defer f.Close() и defer mu.Unlock().
Путают вытеснение с утечкой. Горутина, навсегда заблокированная на канале, никогда не завершится и держит свой стек и всё, на что ссылается. Такие ищут через runtime.NumGoroutine() и профиль горутин в pprof.