← Назад к вопросам

Почему может зависнуть горутина?

1.7 Middle🔥 191 комментариев
#Конкурентность и горутины#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему может зависнуть горутина?

Горутина в Go — это легковесный поток выполнения, управляемый планировщиком Go. В отличие от традиционных системных потоков, горутины могут "зависать" из-за специфических механизмов языка, связанных с планированием, синхронизацией и управлением памятью. Зависание означает, что горутина не может продолжить выполнение, хотя формально не завершилась. Это обычно происходит из-за блокирующих операций или ошибок в логике синхронизации.

Основные причины зависания горутин

1. Блокировка на каналах (Channels)

Каналы — основной инструмент коммуникации между горутинами. Горутина может зависнуть, если:

  • Отправка данных в небуферизованный канал без готового接收чика:
    ch := make(chan int) // Небуферизованный канал
    go func() {
        ch <- 42 // Эта горутина заблокируется, если нет другой горутина, читающей из ch
    }()
    
  • Чтение из пустого канала без отправщика:
    go func() {
        val := <-ch // Зависает, если в канал никто не отправляет данные
    }()
    
  • Использование select с каналами без ветки default:
    select {
        case <-ch1:
        case <-ch2:
        // Если ни ch1, ни ch2 не готовы, горутина заблокируется навсегда без default
    }
    

2. Ожидание на мьютексах (Mutexes) и других примитивах синхронизации

  • Дедлок (Deadlock) из-за неправильного порядка захвата мьютексов:
    var mu1, mu2 sync.Mutex
    go func() {
        mu1.Lock()
        mu2.Lock() // Если другая горутина захватила mu2 первой, здесь возможна блокировка
        // ...
    }()
    
  • Блокировка на sync.WaitGroup, если Done() вызывается меньше раз, чем Add():
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        wg.Done() // Вызван только один раз
    }()
    wg.Wait() // Зависает, ожидая второго вызова Done()
    
  • Блокировка на sync.Cond без корректного сигналирования.

3. Системные или внешние блокировки

  • Вызов системных функций, которые блокируют выполнение (например, сетевые операции без таймаутов).
  • Работа с файловой системой или другими ресурсами, которые могут быть заняты.

4. Проблемы с планировщиком (Scheduler)

  • Горутина, которая никогда не вызывает "сдающих" (yielding) операций — например, бесконечный цикл без вызовов runtime.Gosched() или блокирующих операций. Планировщик Go не является вытесняющим (preemptive) на уровне пользовательского кода в некоторых версиях/ситуациях.
  • Большое количество горутин может привести к нагрузке на планировщик, но это редко вызывает полное зависание одной горутины.

5. Ошибки управления памятью

  • Бесконечное увеличение стека из-за рекурсии без условий выхода может привести к зависанию, хотя обычно это вызывает панику.

6. Логические ошибки в программе

  • Отсутствие условия выхода из цикла в горутине:
    go func() {
        for {
            // Бесконечный цикл без возможности остановки
        }
    }()
    
  • Зависание в состоянии гонки (Race Condition), когда логика программы попадает в непредвиденное состояние.

Как избежать зависаний

  • Всегда используйте таймауты для операций с каналами и внешними ресурсами:
    select {
        case <-ch:
        case <-time.After(5 * time.Second):
            // Обработка таймаута
    }
    
  • Правильно структурируйте синхронизацию: избегайте дедлоков, используйте context.Context для управления жизненным циклом горутин.
  • Профилируйте и мониторьте программу с помощью инструментов Go (pprof, trace).
  • Используйте ветку default в select для неблокирующих операций.

Зависание горутины — это чаще всего симптом ошибки в дизайне программы, особенно в механизмах коммуникации и синхронизации. Понимание этих причин позволяет создавать более надежные и эффективные Go-приложения.

Почему может зависнуть горутина? | PrepBro