В чем разница процессов и потоков для программиста
Что проверяют
Знаете ли вы, что происходит под горутинами, а не только синтаксис go f(). Почти всегда следом спрашивают, чем горутина отличается от потока ОС и как планировщик Go раскладывает их по ядрам.
Как отвечать
Начните с уровня ОС:
- процесс: собственное виртуальное адресное пространство, таблица файловых дескрипторов, свои лимиты и права. Падение одного процесса не портит память другого, а общаются они через IPC: пайпы, сокеты, общую память, сигналы;
- поток: единица планирования внутри процесса. Потоки делят память и дескрипторы, у каждого свой стек и регистры. Переключение между потоками одного процесса дешевле, потому что не меняется таблица страниц. В Linux оба создаются вызовом
clone, разница во флагах того, что разделяется.
Дальше переходите к Go. Горутина это не поток, а задача рантайма, которую планировщик раскладывает по потокам по схеме M:N. Модель GMP: G это горутина, M поток ОС, P логический процессор со своей очередью горутин. Число P задаёт GOMAXPROCS, по умолчанию оно равно числу ядер, а начиная с Go 1.25 учитывает и лимит CPU контейнера. Стек горутины стартует с нескольких килобайт и растёт по мере нужды, поэтому горутин можно держать сотни тысяч, а потоков столько не создашь.
Полезные детали: сетевой ввод-вывод идёт через netpoller (epoll в Linux), и ожидающая горутина поток не занимает. При блокирующем системном вызове рантайм отвязывает P и отдаёт его другому потоку. С Go 1.14 работает асинхронное вытеснение, так что тугой цикл без вызовов функций больше не подвешивает планировщик.
Где спотыкаются
Переносят лёгкость горутин на синхронизацию. Память у горутин общая так же, как у потоков, гонки никуда не деваются, поэтому нужны sync.Mutex, каналы и прогон тестов с флагом -race.
Считают, что чем больше GOMAXPROCS, тем быстрее. В контейнере с лимитом в два ядра лишние P на старых версиях Go давали троттлинг, а не ускорение.
Запускают горутины без способа их остановить. Горутина, которая навсегда ждёт канал, не собирается сборщиком и держит всё, на что ссылается. Для отмены нужен context.Context.