Como semear corretamente o gerador de números aleatórios em Go: segredos de desempenho

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Aleatoriedade é algo sério demais para deixá-la ao acaso

\n\n

Todo mundo que já escreveu código em Go já se deparou com a geração de números aleatórios. O Gerador de Números Aleatórios (RNG) é uma coisa traiçoeira. À primeira vista, parece simples: chame rand.Intn() e obtenha um número. Mas se você não prestar atenção à semeadura correta (seed), sua "aleatoriedade" se transformará em uma sequência previsível que quebrará testes, falhará na criptografia ou simplesmente surpreenderá com resultados idênticos a cada execução.

\n\n

No Stack Overflow, a pergunta "How to properly seed random number generator" acumulou milhares de visualizações. As pessoas se confundem com time.Now().UnixNano(), usam crypto/rand onde não é necessário e sofrem com baixo desempenho. Hoje vamos analisar este tópico usando Go como exemplo — sem enrolação, com benchmarks reais e soluções prontas.

\n\n

Por que a seed não é apenas uma formalidade?

\n\n

Seed (semente) é o estado inicial do gerador. A mesma semente produz a mesma sequência de números. Isso é útil para depuração, mas desastroso para produção se você espera imprevisibilidade.

\n\n

Em Go, o pacote padrão math/rand usa um algoritmo determinístico. Se você esquecer de chamar rand.Seed(), então... Até o Go 1.20, o gerador padrão usava uma semente fixa (geralmente 1). A partir do Go 1.20, math/rand é automaticamente semeado com um valor aleatório na inicialização do programa. No entanto, confiar nisso em locais críticos é uma má prática.

\n\n

O principal problema dos iniciantes é tentar semear o gerador para cada número aleatório:

\n\n
// ❌ RUIM: recriando o gerador toda vez\nfunc getRandom() int {\n    r := rand.New(rand.NewSource(time.Now().UnixNano()))\n    return r.Intn(100)\n}
\n\n

Por que isso é ruim? time.Now().UnixNano() fornece nanossegundos, mas na prática a precisão depende do SO. No Windows, por exemplo, pode ser de ~15 ms. Se você chamar a função duas vezes seguidas, obterá números idênticos. Além disso, cada chamada de rand.NewSource cria um novo objeto, sobrecarregando o coletor de lixo.

\n\n

Top 3 abordagens para semeadura em Go

\n\n

1. Método clássico: seed global uma vez

\n\n

A opção mais simples e correta para a maioria das aplicações é definir a seed uma vez na inicialização do programa.

\n\n
package main\n\nimport (\n    "fmt"\n    "math/rand"\n    "time"\n)\n\nfunc main() {\n    // ✅ BOM: semeamos uma vez\n    rand.Seed(time.Now().UnixNano())\n\n    for i := 0; i < 5; i++ {\n        fmt.Println(rand.Intn(100))\n    }\n}
\n\n

Importante: time.Now().UnixNano() fornece um valor único, mas se o programa for executado várias vezes no mesmo nanossegundo (improvável, mas teoricamente possível), a seed será a mesma. Para 99% das aplicações, isso é suficiente.

\n\n

2. Gerador local para isolamento

\n\n

Quando você tem várias goroutines ou não quer afetar o estado global, crie sua própria instância de rand.Rand.

\n\n
func NewSeededGenerator() *rand.Rand {\n    seed := time.Now().UnixNano()\n    return rand.New(rand.NewSource(seed))\n}\n\n// Uso:\nrng := NewSeededGenerator()\nfor i := 0; i < 3; i++ {\n    fmt.Println(rng.Intn(100))\n}
\n\n

Essa abordagem oferece controle total: diferentes goroutines podem ter seus próprios geradores e não bloqueiam umas às outras (o rand global usa um mutex).

\n\n

3. Seed criptograficamente segura para tarefas críticas

\n\n

Se você precisa de aleatoriedade real (tokens, senhas, criptografia) — esqueça o math/rand. Use crypto/rand:

\n\n
import (\n    "crypto/rand"\n    "encoding/binary"\n    "math/rand"\n)\n\nfunc CryptoSeed() int64 {\n    var buf [8]byte\n    _, err := rand.Read(buf[:])\n    if err != nil {\n                

Blogs

Book Recommendations