Как устроена модель многопоточности в Go и на каких единицах оперируют горутины
Что проверяют
Знаете ли вы, из каких сущностей состоит рантайм Go и какая из них чем управляет. «Горутин и каналов» мало, ждут G, M и P и понимания, что горутина к потоку не привязана. Следом обычно спрашивают, почему горутина дешевле потока и сколько их можно запустить.
Как отвечать
Конкурентность в Go построена на идее CSP: независимые горутины обмениваются данными через каналы. Под этим лежат три единицы планировщика:
- G (горутина): единица работы. Структура рантайма со стеком, указателем инструкции и статусом: runnable, running, waiting, syscall. Стек стартует с 2 КБ и растёт копированием, а не выделяется заранее на мегабайты, как у потока ОС;
- M (machine): поток ОС. Только M реально исполняет инструкции;
- P (processor): право исполнять Go-код плюс ресурсы для этого: локальная очередь горутин, кэш аллокатора памяти. Количество P задаёт
GOMAXPROCS.
Горутина выполняется, когда M держит P и берёт G из очереди. Заблокировалась на канале или sync.Mutex, её паркуют, и M берёт другую. Продолжиться она может уже на другом потоке, поэтому полагаться на конкретный поток нельзя (исключение это runtime.LockOSThread).
Дешевизна горутины складывается из маленького растущего стека и переключения в пространстве пользователя, без системного вызова. Сотни тысяч горутин на обычной машине это нормально, упираются обычно в память и в то, что они делают.
Над этим стоят инструменты синхронизации: каналы для передачи владения данными, sync.Mutex и sync/atomic для общего состояния, sync.WaitGroup и errgroup для ожидания группы, context.Context для отмены.
Где спотыкаются
Говорят, что Go использует пул потоков, а горутина это задача в нём. Модель другая: P с очередями горутин, а число M не фиксировано.
Уверены, что каналы сами по себе защищают от гонок. Рантайм гонки не предотвращает, их ловят через go test -race, а общую память без синхронизации не спасает никакая модель.
Запускают горутину без способа её остановить. Без context или закрытия канала она остаётся висеть, и через неделю сервис съедает память.