Можно ли в Go вручную создать поток уровня операционной системы
Что проверяют
Понимаете ли вы модель планировщика Go: горутины (G), потоки ОС (M) и логические процессоры (P). Следом спрашивают, когда горутину всё-таки нужно привязать к конкретному потоку и сколько потоков на самом деле держит программа.
Как отвечать
Прямого способа «создать поток и запустить в нём функцию» в стандартной библиотеке нет. Потоками управляет рантайм: он запускает их сам и распределяет по ним горутины. Зато есть способы на это повлиять:
runtime.LockOSThread(): привязывает текущую горутину к её потоку. Пока привязка действует, в этом потоке не выполняется ничего другого, а остальные горутины рантайм переносит на другие потоки. Фактически это и есть выделенный поток. Если горутина завершится, не вызвавUnlockOSThread(), поток будет уничтожен вместе с ней;- блокирующие системные вызовы и cgo: поток, застрявший в вызове, отдаёт свой P, и рантайм при нужде запускает новый поток. Поэтому потоков в процессе обычно больше, чем
GOMAXPROCS; - cgo с
pthread_create: создать поток можно, но планировщик Go им не управляет, это уже работа на стороне C.
Зачем привязка нужна на практике: графические библиотеки и OpenGL, которым важен главный поток (вызов LockOSThread в init), переключение пространств имён Linux через setns, C-библиотеки с состоянием в thread-local storage.
Полезно знать ограничения: GOMAXPROCS задаёт число P, то есть сколько горутин исполняют Go-код одновременно, а не число потоков. Предел потоков задаёт debug.SetMaxThreads, по умолчанию 10 000, при превышении программа падает. С Go 1.25 значение GOMAXPROCS по умолчанию учитывает лимит CPU в контейнере.
Где спотыкаются
Говорят, что go f() создаёт поток. Горутина стартует со стеком в несколько килобайт и живёт поверх уже существующих потоков.
Путают GOMAXPROCS с максимумом потоков и не могут объяснить, откуда в профиле двести потоков при GOMAXPROCS=8.
Предлагают os/exec. Это запуск отдельного процесса, к потокам внутри программы он отношения не имеет.