Процессы, потоки и goroutine
Процесс — экземпляр программы с собственным виртуальным адресным пространством и ресурсами ОС. Поток — исполняемый контекст внутри процесса. Goroutine — лёгкая задача, которой управляет рантайм Go: она не является потоком ОС и мультиплексируется на них.
Зачем это на интервью
Эта модель нужна, чтобы объяснить изоляцию сервисов, параллелизм, цену создания задач, отмену работы и диагностику зависаний. На Go-интервью часто проверяют, понимаете ли вы, где заканчивается планировщик рантайма и начинается планировщик ядра.
Минимум для E4
- Различать адресное пространство и ресурсы процесса от стека и регистров потока.
- Объяснять модель Go
G-M-P: goroutine (G) исполняется потоком ОС (M), когда M удерживает логический процессор рантайма (P). - Знать, что общая память потоков и goroutine требует синхронизации; процессы по умолчанию не разделяют пользовательскую память.
- Уметь назвать, когда нужен отдельный процесс, а когда достаточно goroutine или потока.
В Linux отдельную программу обычно запускают через fork/exec: fork создаёт дочерний процесс, а exec заменяет его образ. clone — низкоуровневый системный вызов с флагами: в зависимости от них новая задача может разделять с вызывающей адресное пространство, таблицу файлов, PID namespace и другие ресурсы либо не разделять их. Потоки одного процесса разделяют адресное пространство, открытые файлы и ряд других ресурсов, но имеют отдельные регистры, стек и идентификатор потока. Goroutine начинает с малого растущего стека, а её планирование и парковка — ответственность Go runtime.
Углубление для E5/Senior
Планировщик Go реализует M:N-мультиплексирование: множество G обслуживается набором M. Число P ограничено GOMAXPROCS; оно задаёт, сколько Go-кода может исполняться параллельно, но не является строгим потолком количества потоков ОС. Рантайм может создать дополнительные M для системных вызовов, cgo и служебной работы.
Блокирующий системный вызов может увести M в ядро. Рантайм старается освободить P, чтобы другие goroutine продолжили работу на другом M; детали зависят от вызова и платформы. Не следует считать это гарантией: неограниченные блокирующие операции всё равно ухудшают latency и усложняют shutdown.
Граница процесса полезна для отказоизоляции, отдельных прав, независимого обновления и несовместимых рантаймов. Она дороже передачи данных: IPC требует сериализации и явного протокола. Граница goroutine дешевле, но общая память делает race, утечки и отсутствие backpressure риском всего процесса. Проектируйте отмену через context.Context, ограничивайте конкуренцию и подтверждайте предположения pprof, trace и Linux-инструментами.
Ключевые понятия
- Адресное пространство — отображение виртуальных адресов процесса; другой процесс не может читать его память без специально разрешённого механизма.
- PID/TID — идентификатор процесса и потока в Linux. Поток виден ядру как планируемая задача.
- Стек хранит кадры вызовов конкретного потока или goroutine; heap процесса обычно разделён между его потоками.
- G-M-P — G выполняет пользовательский код, M соответствует потоку ОС, P содержит ресурсы исполнения Go-кода и локальную очередь runnable G.
- Preemption — возможность остановить выполняющуюся задачу, чтобы дать время другой; у Go есть кооперативные и асинхронные механизмы вытеснения.
- IPC — обмен между процессами: pipe, socket, shared memory, очередь сообщений; выбор определяет границы ошибок и стоимость копирования.
Типовые вопросы
- Чем процесс отличается от потока?
- Процесс изолирует адресное пространство и ресурсы; поток — единица исполнения внутри процесса. Потоки дешевле взаимодействуют через общую память, но нуждаются в синхронизации.
- Чем goroutine отличается от потока ОС?
- Goroutine создаётся и планируется рантаймом Go, занимает динамический небольшой стек и может быть припаркована без блокирования потока. Поток ОС планируется ядром и имеет более тяжёлые ресурсы.
- Что означает
GOMAXPROCS?- Это число P и верхняя граница одновременного исполнения Go-кода, не количество goroutine и не точное число M. Значение нужно сверять с доступной CPU-квотой контейнера и нагрузкой.
- Зачем
forkчасто сопровождаетсяexec?forkсоздаёт дочерний процесс как копию текущего;execзаменяет его образ другой программой. Вместе это стандартный путь запуска отдельной команды в Unix.
- Почему запись в
mapиз двух goroutine опасна?- Goroutine делят heap процесса; одновременная запись без синхронизации — data race и может привести к аварийному завершению. Нужны
sync.Mutex, владение данными одной goroutine либо другой согласованный протокол.
- Goroutine делят heap процесса; одновременная запись без синхронизации — data race и может привести к аварийному завершению. Нужны
- Когда запускать работу в новом процессе, а не в goroutine?
- Когда требуются независимые права, лимиты и жизненный цикл, защита от падения/утечки памяти или отдельный исполняемый файл. Для обычной параллельной работы одного сервиса goroutine проще и дешевле.
Практика
- Напишите программу, которая запускает 100 000 goroutine, ожидающих канал. Снимите
runtime.NumGoroutine, RSS процесса и число потоков черезps -o pid,nlwp,rss,cmd -p <pid>; объясните, почему первые два счётчика не равны. - Запустите
go tool traceдля CPU-bound и I/O-bound нагрузки. Найдите состояния runnable, running и waiting и сопоставьте их с планированием CPU. - Реализуйте worker pool с
context.WithCancel, ограничением очереди иWaitGroup. Критерий: при отмене не остаётся goroutine, а producer получает backpressure. - Создайте дочерний процесс через
os/exec, передайте ему данные через stdin и проверьте обработку ненулевого exit code и deadline.
Частые ошибки и ловушки
- Считать, что goroutine создаёт поток ОС или что много goroutine автоматически ускоряют CPU-bound вычисление.
- Запускать goroutine без владельца, отмены и способа дождаться завершения: так возникают утечки и незакрытые ресурсы.
- Передавать изменяемые данные между goroutine без правил владения и надеяться, что race detector заменяет синхронизацию в production.
- Вызывать
forkвручную из многопоточного Go-процесса: используйтеos/exec; послеforkбезопасен лишь ограниченный набор действий доexec. - Путать параллелизм (одновременное выполнение на ядрах) с конкурентностью (структурирование нескольких независимых работ).
Связанные темы
Операционные системы · Планирование CPU и переключение контекста · IPC · Go · Linux и сети · Наблюдаемость
Источники
- Документация Go: Go scheduler в исходном коде runtime, Package runtime и Data Race Detector.
- The Go Programming Language Specification: разделы про goroutine и channels.
man 2 fork,man 2 execve,man 2 clone,man 7 pthreads.- Michael Kerrisk, The Linux Programming Interface, главы о процессах и потоках.