如何在Golang中使用math/rand生成随机数 Go语言伪随机与真随机区别

2次阅读

如何在Golang中使用math/rand生成随机数 Go语言伪随机与真随机区别

math/rand 生成的随机数默认不安全,每次运行结果都一样

直接调用 rand.Intn(10) 得到的永远是 1、5、8、8、2……这种固定序列。因为 rand 包内部用的是确定性算法,且默认种子是 1 —— 它根本没“随机”过。

必须手动用当前时间初始化种子,否则所有程序实例、所有测试、所有 docker 容器跑出来的随机序列完全一致:

rand.Seed(time.Now().UnixNano())

注意:go 1.20+ 已废弃 rand.Seed(),改用新方式(见下一条)。

Go 1.20+ 推荐用 rand.New(rand.NewSource()) 构造独立随机生成器

全局 rand 函数(如 rand.Intn())共享一个包级状态,多 goroutine 并发调用会竞争,还可能被其他库意外重置种子。更稳妥的做法是自己创建隔离的生成器:

立即学习go语言免费学习笔记(深入)”;

  • rand.NewSource(time.Now().UnixNano()) 创建种子源
  • 再用 rand.New() 包装成线程安全的 *rand.Rand 实例
  • 后续所有操作都调用该实例的方法,比如 r.Intn(100)

示例:

r := rand.New(rand.NewSource(time.Now().UnixNano())) n := r.Intn(100) // 每次运行结果不同,且不干扰其他 r 实例

crypto/rand 才是真随机,但开销大、不可复现

crypto/rand 从操作系统熵池读取(linux/dev/urandomwindows 的 BCryptGenRandom),输出不可预测、不可复现,适合生成密钥、Token、salt 等安全敏感场景。

但它不能替代 math/rand 做模拟、测试数据或游戏逻辑 —— 因为:

  • 性能差:每次调用都要进内核,比 math/rand 慢 100 倍以上
  • 不可复现:无法设置种子,调试和单元测试会变得极其困难
  • 可能阻塞:极少数低熵环境(如容器启动初期)下 Read() 可能短暂等待

用法示例(生成 8 字节随机 ID):

buf := make([]byte, 8) _, err := crypto/rand.Read(buf) if err != nil {     panic(err) }

测试时别硬编码 seed,用 t.Setenv 或参数控制

写单元测试时,需要可复现的随机行为,但又不能把 rand.Seed(42) 写死在代码里 —— 这会让所有测试共享同一份随机流,相互干扰。

推荐做法是:让测试通过环境变量或命令行参数注入种子,比如:

  • 测试前设 t.Setenv("RAND_SEED", "12345")
  • 主逻辑中检查 os.Getenv("RAND_SEED"),存在则解析为 int64 并传给 rand.NewSource()
  • CI 中可统一设固定 seed,本地开发则留空,用系统时间

这样既保测试稳定,又不牺牲日常开发的随机性。

真正麻烦的不是选哪个包,而是混用它们:比如误把 crypto/rand 当作 math/rand 的“升级版”去生成抽奖号码 —— 结果服务一压测就卡住,还查不出为什么。

text=ZqhQzanResources