Какие существуют способы мониторинга состояния или завершения работы горутины?

Ответ

В Go нет встроенного механизма для получения статуса конкретной горутины извне (например, 'running', 'blocked'). Состояние горутин управляется исключительно рантаймом. Однако существуют стандартные паттерны для координации их работы и отслеживания завершения.

1. Каналы для сигнализации

Самый простой способ — использовать канал для отправки сигнала о завершении работы.

done := make(chan bool)

go func() {
    // ... какая-то полезная работа ...
    fmt.Println("Горутина завершает работу")
    close(done) // Закрываем канал, чтобы сигнализировать о завершении
}()

// Ожидаем сигнал в блокирующем режиме
<-done
fmt.Println("Основная программа получила сигнал о завершении горутины")

2. sync.WaitGroup для ожидания группы

Идеально подходит, когда нужно запустить несколько горутин и дождаться, пока они все завершат свою работу.

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1) // Увеличиваем счетчик активных горутин
    go func(workerID int) {
        defer wg.Done() // Уменьшаем счетчик по завершении
        fmt.Printf("Воркер %d завершил работуn", workerID)
    }(i)
}

wg.Wait() // Блокируем выполнение, пока счетчик не станет равен нулю
fmt.Println("Все воркеры завершили работу")

3. context.Context для отмены

Контекст позволяет не просто ждать завершения, а управлять жизненным циклом горутины, например, отменить её выполнение.

ctx, cancel := context.WithCancel(context.Background())

go func(ctx context.Context) {
    for {
        select {
        case <-ctx.Done(): // Контекст был отменен
            fmt.Println("Горутина получила сигнал отмены, завершаюсь")
            return
        default:
            // ... выполнение работы ...
            time.Sleep(500 * time.Millisecond)
        }
    }
}(ctx)

time.Sleep(2 * time.Second)
cancel() // Отправляем сигнал отмены всем горутинам, слушающим этот контекст
time.Sleep(1 * time.Second) // Даем время горутине завершиться

Вывод: Выбор способа зависит от задачи: простой сигнал о завершении — канал, ожидание группы — WaitGroup, управление жизненным циклом и каскадная отмена — context.

Ответ 18+ 🔞

Вот же ж, классическая история, как в том анекдоте про Герасима! Хочешь узнать, жива ли твоя горутина, а она тебе — ни гу-гу, как немой крестьянин. В Go, блядь, нет такого встроенного волшебного окошка, чтобы посмотреть на каждую горутину и сказать: "Ага, эта — бежит, а эта — уснула, ждёт канал, как дура". Рантайм сам этим заправляет, а нам, смертным, дают только костыли, но костыли охуенные!

1. Каналы — классика, как "Муму"

Самый простой способ, чтобы горутина крикнула: "Всё, я закончила, можете меня не ждать!". Делаем канал и закрываем его в конце.

done := make(chan bool)

go func() {
    // ... какая-то полезная работа ...
    fmt.Println("Горутина завершает работу")
    close(done) // Закрываем канал, чтобы сигнализировать о завершении
}()

// Ожидаем сигнал в блокирующем режиме
<-done
fmt.Println("Основная программа получила сигнал о завершении горутины")

Вот и всё, элементарно. Главное не забыть закрыть, а то будешь ждать, как Герасим у реки, а сигнала всё нет и нет.

2. sync.WaitGroup — для толпы

А если у тебя не одна горутина, а целая орава, как те ткачихи у царя? Запустил пять штук, и как понять, когда все зашились? Надо ждать последнюю! Для этого есть WaitGroup — счётчик, ёпта.

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1) // Запустили одну — добавили в счётчик, чтоб не потерялась
    go func(workerID int) {
        defer wg.Done() // Сделала дело — отняла себя из счётчика. Обязательно через defer, а то забудешь!
        fmt.Printf("Воркер %d завершил работуn", workerID)
    }(i)
}

wg.Wait() // Стоим тут, пока счётчик не обнулится. Всех дождались!
fmt.Println("Все воркеры завершили работу")

Красота, а не инструмент. Всех дождался и спокоен.

3. context.Context — для полного контроля, чтоб не разбежались

А бывает, что горутина работает, работает, а ты ей сверху: "Знаешь что? Хватит! Завязывай!". Как барыня, которая приказала Муму утопить. Для такого тотального контроля есть контекст.

ctx, cancel := context.WithCancel(context.Background())

go func(ctx context.Context) {
    for {
        select {
        case <-ctx.Done(): // Контекст отменили! Получили команду "стоп"
            fmt.Println("Горутина получила сигнал отмены, завершаюсь")
            return
        default:
            // ... выполнение работы ...
            time.Sleep(500 * time.Millisecond)
        }
    }
}(ctx)

time.Sleep(2 * time.Second)
cancel() // Всё, тварь, закругляйся! Отправляем сигнал отмены.
time.Sleep(1 * time.Second) // Ну даём ей немного времени на прибраться, не животное же

Вот так-то. Контекст — это мощно. Можно не только отменять, но и таймауты ставить, чтобы сама не затянула.

Итог, блядь:
Нужен простой сигнал "сделано" — бери канал.
Нужно дождаться толпу — WaitGroup в помощь.
Нужно управлять, как царь управлял — включать и выключать — context твой выбор.
А статус "running" или "blocked" — это тайна за семью печатями рантайма, туда лучше не лезть, а то охуеешь.