go 1.20+ 中 rand.intn(10) 每次运行结果相同,因全局随机源默认用种子 0;需手动创建 *rand.rand 实例并传入 time.now().unixnano() 等动态种子才能真随机,固定种子则用于可重现场景;区间生成须准确计算长度:[5,15) 用 5+r.intn(10),[5,15] 用 5+r.intn(11)。

Go 语言里生成“看起来随机”的数,不是调个函数就完事——rand.Intn(10) 在 Go 1.20+ 默认每次运行都返回相同结果,这不是 bug,是设计:全局源用固定种子 0。想让它真随机,必须自己构造 *rand.Rand 实例;想它可重现(比如测试、仿真复现),就得主动用固定种子。
为什么 rand.Intn() 总返回同一个数?
因为没初始化随机源。Go 不自动读系统时间或熵池。math/rand 的全局函数(如 rand.Intn、rand.Float64)在未显式设置种子时,内部状态从固定初始值开始,输出序列完全确定。
-
rand.Seed()在 Go 1.20+ 已被移除,硬写会编译失败 - 漏掉
"time"导入,time.Now().UnixNano()恒为0,种子还是固定值 - 哪怕写了
rand.NewSource(time.Now().UnixNano()),但没传给rand.New(),也没用
怎么生成 [min, max) 或 [min, max] 区间的整数?
rand.Intn(n) 只返回 [0, n),边界计算错会导致整个范围偏移。闭区间容易漏 +1,开区间容易多算。
- [5, 15) → 正确:
5 + r.Intn(10)(因为15 - 5 == 10) - [5, 15] → 正确:
5 + r.Intn(11)(因为15 - 5 + 1 == 11) - 输入校验不能少:
if max ,否则 <code>r.Intn(0)或负数会 panic - 浮点同理:
a + r.Float64()*(b-a)得[a, b);要含b需额外处理,通常没必要
并发环境下怎么避免 panic 或数据错乱?
*rand.Rand 实例本身不是并发安全的。多个 goroutine 同时调 r.Intn() 可能 panic,也可能返回负数、超范围值,甚至破坏内部状态。
- 短生命周期场景(如 HTTP handler):每个请求新建一个
*rand.Rand,用不同纳秒时间戳种子,简单干净 - 长周期高吞吐 worker:可用
sync.Pool复用,但Pool.New里不能调time.Now(),否则失去复用意义;更推荐每个 goroutine 自己 new - 全局复用变量?可以,但必须加锁(
sync.Mutex)或改用sync.Pool管理 - 别在循环里反复
rand.New(rand.NewSource(...))—— 时间精度不足可能导致重复种子,且性能差
模拟仿真该用 math/rand 还是 crypto/rand?
二者目标完全不同,混用或错用会出问题:前者快、可重现、适合仿真;后者慢、密码学安全、只该用于 token、密钥等。
- 游戏抽卡、蒙特卡洛积分、用户行为模拟 → 用
math/rand(或math/rand/v2),快且可控 - 生成 API token、session ID、加密 salt → 必须用
crypto/rand,math/rand有被预测风险 -
math/rand/v2是零配置、线程安全、不可重现的替代方案,但不兼容旧代码(无Float64()、不支持自定义源) - 测试时反而要故意用固定种子(如
rand.NewSource(42)),确保结果可复现;生产环境必须用真随机源
真正难的不是写对一行 r := rand.New(rand.NewSource(time.Now().UnixNano())),而是判断这个随机数到底要“可重现”还是“不可预测”,以及它跑在单 goroutine 还是并发环境里——选错路径,轻则结果总一样,重则服务间歇性返回负数或 panic。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











