Comment initialiser correctement un générateur de nombres aléatoires en Go : secrets de performance

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Le hasard est une chose trop sérieuse pour être laissée au hasard

\n\n

Quiconque a déjà écrit du code en Go a été confronté à la génération de nombres aléatoires. Le générateur de nombres aléatoires (RNG) est une chose perfide. En apparence, tout est simple : on appelle rand.Intn() et on obtient un nombre. Mais si on ne prête pas attention à l'initialisation correcte (seed), votre « hasard » se transforme en une séquence prévisible qui cassera les tests, décevra en cryptographie ou surprendra simplement par des résultats identiques à chaque exécution.

\n\n

Sur Stack Overflow, la question « How to properly seed random number generator » a recueilli des milliers de vues. Les gens se perdent avec time.Now().UnixNano(), utilisent crypto/rand là où ce n'est pas nécessaire, et souffrent de faibles performances. Aujourd'hui, nous allons analyser ce sujet avec Go — sans blabla, avec de vrais benchmarks et des solutions prêtes à l'emploi.

\n\n

Pourquoi le seed n'est-il pas une simple formalité ?

\n\n

Le seed (germe) est l'état initial du générateur. Un même germe donne une même séquence de nombres. C'est pratique pour le débogage, mais désastreux en production si vous attendez de l'imprévisibilité.

\n\n

En Go, le package standard math/rand utilise un algorithme déterministe. Si vous oubliez d'appeler rand.Seed(), alors... Avant Go 1.20, le générateur par défaut utilisait un germe fixe (généralement 1). Depuis Go 1.20, math/rand est automatiquement initialisé avec une valeur aléatoire au démarrage du programme. Cependant, se fier à cela dans des endroits critiques est une mauvaise pratique.

\n\n

Le principal problème des débutants est de tenter d'initialiser le générateur pour chaque nombre aléatoire :

\n\n
// ❌ MAUVAIS : on recrée le générateur à chaque fois\nfunc getRandom() int {\n    r := rand.New(rand.NewSource(time.Now().UnixNano()))\n    return r.Intn(100)\n}
\n\n

Pourquoi est-ce mauvais ? time.Now().UnixNano() donne des nanosecondes, mais en réalité la précision dépend du système d'exploitation. Sous Windows, par exemple, elle peut être d'environ 15 ms. Si vous appelez la fonction deux fois de suite, vous obtiendrez des nombres identiques. De plus, chaque appel à rand.NewSource crée un nouvel objet, ce qui sollicite le ramasse-miettes.

\n\n

Top 3 des approches pour l'initialisation en Go

\n\n

1. Méthode classique : un seed global une seule fois

\n\n

L'option la plus simple et la plus correcte pour la plupart des applications est de définir le seed une seule fois au démarrage du programme.

\n\n
package main\n\nimport (\n    "fmt"\n    "math/rand"\n    "time"\n)\n\nfunc main() {\n    // ✅ BON : on initialise une seule fois\n    rand.Seed(time.Now().UnixNano())\n\n    for i := 0; i < 5; i++ {\n        fmt.Println(rand.Intn(100))\n    }\n}
\n\n

Important : time.Now().UnixNano() donne une valeur unique, mais si le programme est exécuté plusieurs fois dans la même nanoseconde (peu probable, mais théoriquement possible), le seed sera identique. Pour 99 % des applications, cela suffit.

\n\n

2. Générateur local pour l'isolation

\n\n

Lorsque vous avez plusieurs goroutines ou que vous ne voulez pas influencer l'état global, créez votre propre instance de rand.Rand.

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

Cette approche offre un contrôle total : différentes goroutines peuvent avoir leurs propres générateurs, et elles ne se bloquent pas mutuellement (le rand global utilise un mutex).

\n\n

3. Seed cryptographique pour les tâches critiques

\n\n

Si vous avez besoin d'un vrai hasard (jetons, mots de passe, cryptographie) — oubliez math/rand. Utilisez 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