Выделение на стеке - это по сути сдвиг указателя стека при входе в функцию и сдвиг обратно при выходе. Ни аллокатор, ни GC не участвуют. Выделение в куче требует аллокатора и в дальнейшем работы GC для освобождения.
func onStack() {
var buf [1024]byte // массив на стеке
use(buf[:])
}
func onHeap() {
buf := make([]byte, 1024) // данные в куче, слайс на стеке
}
У горутины стек начинается с нескольких КБ и при необходимости растёт (и сжимается). Миллионы горутин возможны именно из-за лёгких стеков.