Go Internals - Essenciais
Introdução
Depois da série Estruturas de Dados com Golang, o próximo passo é olhar por baixo do capô. Não como usar slices, maps ou goroutines, mas como a linguagem e o runtime de fato os implementam.
Este artigo é Essenciais: um mapa do terreno. Cada artigo seguinte faz um mergulho profundo em uma camada dos internals de Go — Scheduler, Memória, Allocator, Garbage Collector, Tipos Built-in, Interfaces, Concorrência e Compilador. Vamos ler código-fonte real, rodar experimentos pequenos e ligar comportamentos que você já conhece no código de aplicação à maquinaria por trás.
A série inteira usa Go 1.23. Internals mudam entre releases; quando algo mudar em uma versão futura, destacamos no artigo correspondente.
O que "Go internals" cobre de verdade
Quando alguém fala em "internals de Go," em geral são dois sistemas que trabalham juntos:
- A toolchain (
cmd/compile,cmd/link,cmd/asm) — transforma seus arquivos.goem um binário. - O runtime (
runtime) — linkado em todo programa Go e responsável por scheduling, memória, GC, stacks e a implementação de recursos centrais da linguagem.
Sua função main não é o ponto de entrada real. O linker prepara o runtime primeiro; só depois o seu código roda.
flowchart LR
subgraph toolchain["Toolchain"]
A["fontes .go"] --> B["compile"]
B --> C["link"]
end
C --> D["executável"]
D --> E["init do runtime"]
E --> F["main.main()"]
A maior parte dos artigos foca no runtime desse diagrama: o que acontece depois que o binário sobe, e o que o compilador precisou preparar para o runtime funcionar. Compilador fecha a série no lado da toolchain — como o fonte vira o código e os metadados que o runtime consome.
Roteiro da série
O runtime não é um bloco monolítico. Ele é organizado em camadas que dependem umas das outras. Artigos posteriores assumem os anteriores; por isso a série é vertical, e não um único post gigante.
flowchart TB
subgraph sched["Scheduling"]
GMP["Scheduler G / M / P"]
end
subgraph mem["Memória"]
STK["Stacks e escape analysis"]
ALLOC["Allocator de heap"]
GC["Garbage collector"]
end
subgraph lang["Maquinaria da linguagem"]
BUILTIN["Slices, strings, maps"]
IFACE["Interfaces e dispatch"]
SYNC["Channels e sync primitives"]
end
GMP --> STK
STK --> ALLOC
ALLOC --> GC
GC --> BUILTIN
BUILTIN --> IFACE
GMP --> SYNC
IFACE --> SYNC
subgraph tool["Toolchain"]
COMP["Pipeline do compilador"]
end
IFACE --> COMP
SYNC --> COMP
| Artigo | Camada | O que vamos aprofundar |
|---|---|---|
| Essenciais (este artigo) | Visão geral | Pipeline de build, papel do runtime, como ler o fonte |
| Scheduler | Scheduling | Goroutines vs threads do SO, G/M/P, work stealing, preempção |
| Memória | Modelo de memória | Crescimento de stack, heap, escape analysis, quando valores saem da stack |
| Allocator | Alocação em heap | Size classes, spans, mcache / mcentral / mheap |
| Garbage Collector | Reclamação | Marcação tri-color, write barriers, fases, STW, GOGC |
| Tipos Built-in | Tipos centrais | Header de slice, layout de string, internals de map (hmap) |
| Interfaces | Dispatch | iface / eface, itabs, custo de dispatch dinâmico |
| Concorrência | Primitivas de sync | Buffers de channel, select, mutex e semáforos |
| Compilador | Toolchain | SSA, inlining, eliminação de bounds checks, o que o compilador emite para o runtime |
Tipos Built-in faz ponte de volta à série de estruturas de dados: você já implementou hash tables; vamos ver o que o map do runtime faz de diferente.
De go build a um programa rodando
Um programa mínimo já ancora o resto da série:
package main
import "fmt"
func main() {
fmt.Println("hello")
}
go build -o hello .
./hello
Por baixo dos panos, go build chama o compilador e o linker. O compilador gera código de máquina e metadados que o runtime precisa: stack maps, descritores de tipo, informação de reloc. O linker produz o executável com o pacote runtime linkado.
Na subida do processo, o runtime se inicializa (scheduler, arenas de memória, GC, signal handlers) antes de main.main rodar. Se você nunca percorreu esse caminho, vale abrir src/runtime/proc.go e buscar runtime.main uma vez; Scheduler volta nesse ponto.
Como acompanhar os artigos
Não é obrigatório fazer fork do repositório Go, mas você precisa do fonte da stdlib e do runtime em Go 1.23 — a mesma versão da sua toolchain.
go version # deve reportar go1.23.x
go env GOROOT
O código do runtime fica em $GOROOT/src/runtime/. O do compilador, em $GOROOT/src/cmd/compile/. Navegar em go.dev/src é a mesma árvore; escolha a tag go1.23 para acompanhar esta série.
Hábitos úteis para os mergulhos:
- Reprodutores pequenos — dez linhas de Go que mostram um comportamento (escape, pressão de GC, churn de goroutines).
- Feedback do compilador —
go build -gcflags="-m"para escape analysis (Memória). - Trace do runtime —
GODEBUG=gctrace=1,...para GC (Garbage Collector), traces de scheduler onde fizer sentido (Scheduler). - Debugger — Delve para inspecionar frames e estado de goroutines enquanto lê conceitos de scheduler.
Não vamos tratar unsafe como objetivo. Ele aparece só onde esclarece como o runtime representa valores na memória.
O que você já deve saber
A série é mais profunda que os artigos de estruturas de dados, mas não é tutorial de Go. Basta ler com tranquilidade:
- ponteiros, structs e interfaces no nível da linguagem
- goroutines e channels no dia a dia
- noção básica de complexidade (
O(1)vsO(n))
Se você terminou a série de estruturas de dados, está no lugar certo.
Diferença em relação à série de estruturas de dados
| Série de estruturas de dados | Série Go internals | |
|---|---|---|
| Objetivo | Implementar e comparar estruturas | Explicar runtime e maquinaria da linguagem |
| Foco em código | Suas implementações de Container |
Programas pequenos + leitura do runtime |
| Profundidade | Algoritmo + trade-offs de API | Layout de memória, syscalls, invariantes do scheduler |
| Divisão | Uma categoria de estrutura por artigo | Uma camada dos internals por artigo |
Mesmo tom, mesma preferência por exemplos concretos — mas o "artefato" é entendimento, não uma biblioteca reutilizável.
Conclusão
Essenciais traça a rota: a toolchain monta o binário, o runtime assume execução e memória, e cada artigo seguinte isola uma camada para um mergulho de verdade.
Em seguida vem Scheduler — como goroutines multiplexam threads do SO, o que significam G, M e P, e por que seu código concorrente se comporta como se comporta sob carga.