go中math/rand需显式seed(如time.now().unixnano())避免固定序列,高并发应使用独立rand.rand实例或sync.pool;crypto/rand仅适用于密钥等高安全场景,性能差且非必要勿用。

Go 里没有“高性能真随机数”这回事——crypto/rand 的设计目标是安全,不是快;而 math/rand 的快,恰恰建立在它不安全、可复现的基础上。选错包或混用两者,轻则测试失败、并发 panic,重则密钥泄露。
为什么 rand.Intn(100) 每次都返回相同数字
因为 math/rand 默认用固定种子 1 初始化,整个包级状态是确定性的。没显式设置种子源,就永远在跑同一串序列:81、52、94……无论你重启多少次、换多少台机器。
- Go 1.19 及更早:必须调用
rand.Seed(time.Now().UnixNano())(已废弃) - Go 1.20+:全局函数不可靠,应改用实例化方式:
r := rand.New(rand.NewSource(time.Now().UnixNano())) - 切勿在
init()或包级变量中初始化全局rand——多个库互相覆盖种子会导致行为失控
并发环境下直接用全局 rand.Intn 会 panic
所有 rand.* 全局函数(如 rand.Intn、rand.Float64)共享同一个包级 *rand.Rand 实例,goroutine 并发调用时会竞争内部状态,Go 1.20+ 已默认加锁但仍有性能抖动,旧版本甚至直接 panic。
- 正确做法:每个 goroutine 或逻辑单元持有自己的
*rand.Rand实例 - 若需跨 goroutine 复用,用
sync.Pool缓存*rand.Rand实例,避免重复创建开销 - 示例池化写法:
var randPool = sync.Pool{New: func() any { return rand.New(rand.NewSource(time.Now().UnixNano())) }}
crypto/rand 不适合做游戏/模拟/测试数据
crypto/rand.Read 调用一次要进内核读 /dev/urandom,比 math/rand 慢 100 倍以上,且无法设种子、不可复现——调试时你永远不知道下一次生成的 ID 是什么,CI 测试会随缘失败。
- 只在以下场景用
crypto/rand:token、salt、AES key、nonce - 生成 16 字节 token 示例:
buf := make([]byte, 16); _, _ = crypto/rand.Read(buf) - 想兼顾“安全”和“可控”?别自己造轮子——用
golang.org/x/crypto/chacha20poly1305等封装好的加密原语,而非裸用crypto/rand做业务逻辑
生成 [min, max] 区间整数时的整数溢出陷阱
常见写法 min + r.Intn(max-min+1) 在 min 和 max 接近 int64 极值时,max - min 会溢出为负数,导致 Intn panic。
- 安全边界检查必须做:
if max - 对大整数范围,改用
r.Int63n(uint64(max) - uint64(min) + 1)配合类型转换 - 日常开发中若确定
max - min ,优先用 <code>r.Int31n—— 它比Intn快,且无 64 位溢出风险
真正容易被忽略的点是:很多人以为“只要用了 time.Now().UnixNano() 就万事大吉”,却没意识到容器启动瞬间纳秒时间戳可能高度重复,尤其在 Kubernetes 批量拉起 Pod 时——这时候建议 fallback 到 time.Now().Unix() + 进程 PID 混合哈希,或者直接用 crypto/rand 生成初始种子(仅用于初始化 math/rand)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











