Como passar em uma entrevista de Go: principais perguntas de nível avançado
A entrevista para a posição de desenvolvedor Go não é apenas uma verificação de sintaxe. No nível avançado, os entrevistadores querem ver uma compreensão profunda de concorrência, gerenciamento de memória, funcionamento do escalonador e expressões idiomáticas da linguagem. Neste artigo, analisaremos as principais perguntas frequentemente feitas a engenheiros seniores, com exemplos de código e explicações. O material será útil tanto para preparação quanto para sistematização do conhecimento.
1. Concorrência: goroutines, canais e primitivas de sincronização
Go é famoso por seu suporte embutido à concorrência. Os entrevistadores quase sempre verificam como o candidato entende a diferença entre goroutines e threads do SO, e se sabe usar corretamente canais e o pacote sync.
Pergunta: Qual é a diferença entre uma goroutine e uma thread do SO?
Uma goroutine é uma thread leve, gerenciada pelo escalonador do Go (modelo G-M-P). Ela tem sua própria pilha, que começa com 2-4 KB e pode crescer dinamicamente. Uma thread do SO é um objeto pesado com uma pilha fixa (geralmente 1-8 MB). Milhares de goroutines podem rodar em uma única thread do SO, tornando sua criação e alternância muito baratas.
Pergunta: Como evitar condições de corrida (data race) ao trabalhar com goroutines?
Use canais para transmitir dados entre goroutines ("Não se comunique compartilhando memória; em vez disso, compartilhe memória se comunicando") ou mutexes (sync.Mutex, sync.RWMutex). Sempre verifique o código com a flag -race.
var counter intvar mu sync.Mutex
func increment() { mu.Lock() counter++ mu.Unlock()}Pergunta: Explique o princípio de funcionamento do select. O que acontece se todos os canais estiverem bloqueados?
select permite que uma goroutine espere por várias operações de canal simultaneamente. Se todos os canais estiverem bloqueados, o select esperará até que um deles se torne pronto. Se houver um default, ele será executado imediatamente, evitando o bloqueio.
select {case msg := <-ch1: fmt.Println("Recebido de ch1:", msg)case ch2 <- "data": fmt.Println("Enviado para ch2")default: fmt.Println("Nenhum canal está pronto")}2. Gerenciamento de memória e coletor de lixo (GC)
Para o nível avançado, é importante entender como funciona a alocação em Go, onde as variáveis são colocadas (pilha/monte) e como o coletor de lixo afeta o desempenho.
Pergunta: Como o Go decide colocar uma variável na pilha ou no monte?
Go usa análise de escape (escape analysis). Se o compilador puder provar que uma variável não é usada após o retorno da função (não "escapa" para fora), ela é colocada na pilha. Caso contrário, no monte. Por exemplo, retornar um ponteiro para uma variável local faz com que ela "escape" para o monte.
func createUser() *User { u := User{Name: "Alice"} return &u // escape para o monte}Pergunta: Como funciona o coletor de lixo em Go? Quais fases ele percorre?
O GC em Go é um coletor de lixo paralelo tricolor (mark-sweep). Ele passa por três fases:
- Mark Setup (STW): uma breve parada do mundo para ativar a write barrier.
- Marking: fase paralela que marca objetos vivos. Funciona junto com a aplicação.
- Sweep: limpeza da memória não utilizada, geralmente adiada.
O tempo STW (Stop The World) é geralmente < 500 µs, tornando o Go adequado para aplicações sensíveis à latência.
Pergunta: Como reduzir a carga no GC?
- Evite alocações frequentes (use pools de objetos
sync.Pool). - Reutilize slices com
slice[:0]. - Aumente o valor de