Ответ
У горутины есть три основных состояния с точки зрения планировщика Go:
Running
(Выполняется) — горутина в данный момент исполняет свой код на логическом процессоре (P), который привязан к потоку операционной системы (M).Runnable
(Готова к выполнению) — горутина готова исполнять код, но ожидает своей очереди на свободный логический процессор. Такие горутины находятся в локальной или глобальной очереди планировщика.Waiting
(Ожидание) — горутина заблокирована и не может выполняться. Она не потребляет ресурсы CPU и ждет внешнего события для разблокировки. Причины блокировки:- Системный вызов (например, чтение файла с диска).
- Ожидание на канале (
<-ch
). - Ожидание мьютекса (
mutex.Lock()
). - Ожидание таймера (
time.Sleep()
).
Ключевая разница между Runnable
и Waiting
:
Runnable
— это активное ожидание. Горутина хочет и может работать, но ждет процессорного времени.Waiting
— это пассивное ожидание. Горутина не может работать, пока не выполнится внешнее условие (например, придут данные в канал).
Состояние | Описание | Потребление CPU | Пример |
---|---|---|---|
Runnable | Готова к выполнению, в очереди планировщика | Нет | Горутина, только что созданная через go , или та, что вернулась из состояния Waiting |
Waiting | Заблокирована (канал, мьютекс, syscall) | Нет | time.Sleep() , <-ch , mutex.Lock() |
Running | Выполняется на процессоре | Да | Исполнение простого кода, например, a := 1 + 1 |
func main() {
ch := make(chan int)
go func() {
// Сразу после запуска горутина становится Runnable.
// Затем планировщик переводит ее в Running.
time.Sleep(time.Second) // Переходит в Waiting на 1 секунду.
// После пробуждения снова становится Runnable, а затем Running.
ch <- 1 // Может перейти в Waiting, если канал небуферизованный и нет читателя.
}()
// Основная горутина переходит в Waiting, пока не получит данные из канала.
<-ch
}