Voltar ao início

Go Internals - Essenciais

6 min de leitura
Cover Image for Go Internals - Essenciais
Lucas LemosLucas Lemos

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:

  1. A toolchain (cmd/compile, cmd/link, cmd/asm) — transforma seus arquivos .go em um binário.
  2. 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 compiladorgo build -gcflags="-m" para escape analysis (Memória).
  • Trace do runtimeGODEBUG=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) vs O(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.