Как горутина переходит в блокированное состояние?

Ответ

Горутина переходит в блокированное состояние, когда она вызывает операцию, которая требует ожидания внешнего события или ресурса. В этот момент планировщик Go (scheduler) снимает горутину с выполнения на логическом процессоре (P) и переключается на другую готовую к выполнению горутину. Заблокированная горутина не потребляет ресурсы CPU.

Примеры операций, вызывающих блокировку:

  • Операции с каналами: Чтение из пустого канала (<-ch) или запись в полный канал (ch <- val).
  • Мьютексы и синхронизационные примитивы: Ожидание освобождения мьютекса (sync.Mutex.Lock()) или других примитивов (sync.WaitGroup.Wait(), sync.Cond.Wait()).
  • Системные вызовы (Syscalls): Вызовы к операционной системе, такие как чтение/запись файлов, сетевые операции, или CGO вызовы.
  • Таймеры и задержки: Например, time.Sleep().

Механизм блокировки:
Когда горутина блокируется, её состояние изменяется (например, на _Gwaiting для ожидания или _Gsyscall для системного вызова), и она удаляется из очереди выполнения текущего P. P становится свободным и немедленно начинает выполнять следующую горутину из своей локальной очереди или ищет работу в других местах (например, в глобальной очереди).

Важные моменты:

  1. Эффективность: Блокированная горутина не потребляет CPU. Она просто ожидает, пока условие для её разблокировки не будет выполнено.
  2. Автоматическое возобновление: Как только условие блокировки снимается (например, данные появились в канале, мьютекс освободился, системный вызов завершился), планировщик Go автоматически делает горутину снова готовой к выполнению и помещает её в очередь.
  3. I/O-операции и неблокирующие вызовы: Для большинства I/O-операций (особенно сетевых) Go runtime использует неблокирующие системные вызовы и механизм событий (например, epoll на Linux, kqueue на macOS). Это позволяет потоку ОС (M) не блокироваться, а продолжать выполнять другие горутины, пока I/O-операция не завершится. Для некоторых других блокирующих системных вызовов (например, CGO, некоторые файловые операции) поток ОС может быть заблокирован, но Go runtime способен создать новый поток ОС, чтобы другие горутины продолжали выполняться, поддерживая высокую конкурентность.