Сколько потоков запускается в runtime приложении Go по умолчанию
Что проверяют
Различаете ли вы горутину, поток операционной системы и GOMAXPROCS. Вопрос звучит как загадка с числом, а проверяют модель планировщика: кто кого исполняет и откуда берутся лишние потоки.
Как отвечать
Фиксированного числа нет, но их точно больше одного. Даже пустая программа поднимает главный поток, отдельный поток под sysmon, который следит за длинными системными вызовами и сборщиком, плюс потоки под нужды планировщика и сборки мусора. Считать их руками бессмысленно, число плавает от версии к версии.
Дальше разложите модель. Планировщик работает по схеме M к N: горутины G раскладываются по логическим процессорам P, а исполняют их потоки M. GOMAXPROCS задаёт количество P, то есть максимум потоков, одновременно выполняющих код на Go, а не общее число потоков процесса. По умолчанию это число логических ядер; в свежих версиях рантайм учитывает и лимит CPU в cgroup, раньше для этого в контейнеры тянули отдельную библиотеку.
Общее число потоков растёт отдельно. Горутина, ушедшая в блокирующий системный вызов или в cgo, отцепляет свой M от P, и рантайм заводит новый поток, чтобы процессор не простаивал. Поэтому у сервиса с тяжёлым файловым или cgo вводом-выводом в профиле threadcreate спокойно видны сотни потоков при восьми P. Верхняя граница есть, порядка десяти тысяч, при её достижении процесс падает.
Где спотыкаются
Отвечают «один поток» или «ровно GOMAXPROCS». Первое путает поток с главной горутиной, второе выдаёт лимит на исполнение кода Go за общее число потоков.
Считают горутину лёгким потоком и на этом останавливаются. Горутина стартует со стеком в пару килобайт, который растёт по мере надобности, и переключается внутри рантайма без системного вызова, поток же выделяет ядро ОС, и стоит он на порядки дороже.
Упёршись в лимит потоков, поднимают его вместо того, чтобы убрать причину. Лимит срабатывает, когда тысячи горутин одновременно висят в блокирующих вызовах, и лечится это ограничением конкурентности через семафор или пул воркеров.