Zufall ist eine zu ernste Angelegenheit, um ihn dem Zufall zu überlassen
\n\nJeder, der jemals Code in Go geschrieben hat, ist auf die Generierung von Zufallszahlen gestoßen. Der Zufallszahlengenerator (RNG) ist eine tückische Sache. Auf den ersten Blick ist alles einfach: rand.Intn() aufgerufen – Zahl erhalten. Aber wenn man der richtigen Seed-Initialisierung keine Aufmerksamkeit schenkt, wird aus Ihrer „Zufälligkeit“ eine vorhersagbare Sequenz, die Tests zerstört, in der Kryptografie versagt oder Sie einfach mit identischen Ergebnissen bei jedem Start überrascht.
Auf Stack Overflow hat die Frage „How to properly seed random number generator“ tausende Aufrufe gesammelt. Leute verwirren sich mit time.Now().UnixNano(), verwenden crypto/rand dort, wo es nicht nötig ist, und leiden unter geringer Leistung. Heute analysieren wir dieses Thema am Beispiel von Go – ohne überflüssige Worte, mit echten Benchmarks und fertigen Lösungen.
Warum ist der Seed nicht nur eine Formalität?
\n\nSeed (Saatkorn) ist der Anfangszustand des Generators. Dasselbe Saatkorn erzeugt dieselbe Zahlenfolge. Das ist praktisch zum Debuggen, aber tödlich für die Produktion, wenn Sie Unvorhersagbarkeit erwarten.
\n\nIn Go verwendet das Standardpaket math/rand einen deterministischen Algorithmus. Wenn Sie vergessen, rand.Seed() aufzurufen, dann... Bis Go 1.20 verwendete der Generator standardmäßig ein festes Saatkorn (normalerweise 1). Ab Go 1.20 wird math/rand automatisch mit einem zufälligen Wert beim Start des Programms geseedet. Sich jedoch an kritischen Stellen darauf zu verlassen, ist eine schlechte Praxis.
Das Hauptproblem von Anfängern ist der Versuch, den Generator bei jeder Zufallszahl neu zu seeden:
\n\n// ❌ SCHLECHT: Generator jedes Mal neu erstellen\nfunc getRandom() int {\n r := rand.New(rand.NewSource(time.Now().UnixNano()))\n return r.Intn(100)\n}\n\nWarum ist das schlecht? time.Now().UnixNano() liefert Nanosekunden, aber in der Realität hängt die Genauigkeit vom Betriebssystem ab. Unter Windows kann sie beispielsweise ~15 ms betragen. Wenn Sie die Funktion zweimal hintereinander aufrufen, erhalten Sie identische Zahlen. Außerdem erzeugt jeder Aufruf von rand.NewSource ein neues Objekt, was den Garbage Collector belastet.
Die Top-3 Ansätze zum Seeden in Go
\n\n1. Klassischer Weg: Globaler Seed einmalig
\n\nDie einfachste und korrekteste Option für die meisten Anwendungen ist es, den Seed einmal beim Start des Programms zu setzen.
\n\npackage main\n\nimport (\n "fmt"\n "math/rand"\n "time"\n)\n\nfunc main() {\n // ✅ GUT: Einmal seeden\n rand.Seed(time.Now().UnixNano())\n\n for i := 0; i < 5; i++ {\n fmt.Println(rand.Intn(100))\n }\n}\n\nWichtig: time.Now().UnixNano() liefert einen eindeutigen Wert, aber wenn das Programm mehrmals in derselben Nanosekunde gestartet wird (unwahrscheinlich, aber theoretisch möglich), ist der Seed identisch. Für 99% der Anwendungen reicht dies aus.
2. Lokaler Generator zur Isolation
\n\nWenn Sie mehrere Goroutinen haben oder den globalen Zustand nicht beeinflussen möchten, erstellen Sie Ihre eigene Instanz von rand.Rand.
func NewSeededGenerator() *rand.Rand {\n seed := time.Now().UnixNano()\n return rand.New(rand.NewSource(seed))\n}\n\n// Verwendung:\nrng := NewSeededGenerator()\nfor i := 0; i < 3; i++ {\n fmt.Println(rng.Intn(100))\n}\n\nDieser Ansatz bietet volle Kontrolle: Verschiedene Goroutinen können ihre eigenen Generatoren haben und blockieren sich nicht gegenseitig (das globale rand verwendet einen Mutex).
3. Kryptografisch sicherer Seed für verantwortungsvolle Aufgaben
\n\nWenn Sie echte Zufälligkeit benötigen (Tokens, Passwörter, Kryptografie) – vergessen Sie math/rand. Verwenden Sie crypto/rand:
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