Ответ
Да, такое возможно, особенно в старых версиях Go. Это связано с моделью кооперативной многозадачности, которую использует планировщик Go.
Планировщик Go не может прервать горутину в любой момент времени. Он ожидает, пока горутина сама уступит управление, достигнув точки вытеснения (preemption point). Такими точками являются:
- Вызовы функций (с Go 1.14 это стало надежнее).
- Операции с каналами.
- Системные вызовы (например, чтение файла или сетевое взаимодействие).
- Явный вызов
runtime.Gosched()
.
Проблема в старых версиях Go (до 1.14)
Если горутина выполняла долгий цикл без точек вытеснения, она могла монополизировать поток ОС (P), не давая другим горутинам из очереди этого потока выполняться.
// В Go < 1.14 этот код заблокирует поток ОС
func main() {
runtime.GOMAXPROCS(1) // Ограничимся одним потоком для наглядности
go func() {
// Этот цикл не содержит точек вытеснения
for {}
}()
// Эта горутина, скорее всего, никогда не получит управление
go func() {
println("Hello from another goroutine!")
}()
time.Sleep(time.Second)
println("Done")
}
Решение в современных версиях Go (1.14+)
Начиная с версии Go 1.14, была введена асинхронная вытесняющая многозадачность (asynchronous preemption). Теперь компилятор вставляет специальные проверки в начало функций и в циклы. Если горутина выполняется слишком долго (десятки миллисекунд), планировщик может принудительно приостановить её и передать управление другой горутине.
Это делает проблему "зависания" из-за долгих циклов гораздо менее вероятной, но не устраняет её полностью. Например, в циклах без вызовов функций на некоторых архитектурах проблема может сохраняться.
Вывод
- Да, горутина может заблокировать поток, если выполняет долгую, непрерывную вычислительную работу без точек вытеснения.
- В Go 1.14+ эта проблема в значительной степени решена благодаря асинхронному вытеснению, но понимание механизма остается важным.
- Для ручного управления можно использовать
runtime.Gosched()
, чтобы явно указать планировщику, что горутина готова уступить процессор.