go 1.20+ 必须用 math/rand/v2,因其默认使用加密安全熵源、线程安全且无需手动设种子;旧版 math/rand 全局源默认种子为1,不初始化则随机数序列完全重复。

直接说结论:Go 1.20+ 必须用 math/rand/v2(推荐),旧版 math/rand 的全局随机源不安全、不可预测,且默认种子是固定值(1),不显式设置种子会导致每次运行生成完全相同的随机数序列。
为什么调用 rand.Intn(10) 总是返回相同数字?
因为没初始化种子,math/rand 使用全局伪随机源,其默认种子是硬编码的 1。只要没调用 rand.Seed()(Go rand.Rand 实例(Go ≥ 1.20),结果就确定性重复。
- Go rand.Seed(time.Now().UnixNano()),但这是全局副作用,多 goroutine 并发调用会竞态
- Go ≥ 1.20:
math/rand已弃用,应改用math/rand/v2,它默认使用加密安全的熵源,无需手动设种子 - 若仍用旧包且需兼容,务必在
main()开头只调用一次rand.Seed(),不要在函数内反复调用
Go 1.20+ 正确用法:优先用 math/rand/v2
math/rand/v2 是零配置、线程安全、默认不可预测的现代方案。它自动从系统获取熵(如 /dev/urandom),不依赖时间戳,也不暴露种子控制接口——这是设计取舍:牺牲可重现性,换取安全性与简洁性。
- 生成 [0, 10) 整数:
rand.IntN(10) - 生成 [5, 15) 整数:
rand.IntN(10) + 5 - 生成浮点数:
rand.Float64()([0.0, 1.0))、rand.NormFloat64()(标准正态分布) - 如需可重现结果(如测试),必须降级用旧包并显式传入种子,
v2不支持
需要可重现随机序列时:用 math/rand.New + 自定义种子
当写单元测试、模拟或调试需要固定输出时,不能依赖全局状态,必须构造独立的 *rand.Rand 实例。
立即学习“go语言免费学习笔记(深入)”;
src := rand.NewSource(42) // 固定种子 r := rand.New(src) fmt.Println(r.Intn(100)) // 每次运行都返回相同序列
- 种子类型是
rand.Source,常用rand.NewSource(int64) - 避免用
time.Now().UnixNano()作测试种子——它会破坏可重现性 - 并发场景下,每个 goroutine 应持有自己的
*rand.Rand实例,不要共享 - 旧包的
rand.Read()等方法操作的是全局源,有竞态风险;必须用实例方法如r.Read()
真正麻烦的地方不在语法,而在于混淆「可重现」和「不可预测」的需求:生产环境该用 v2,测试才用带种子的旧方式。很多人卡在第一次跑出相同数字就以为代码错了,其实只是还没理解 Go 随机数的设计哲学——它把“安全默认”放在了第一位。










