rand.intn并发卡住是因为其依赖全局rand.rand实例,所有方法被sync.mutex串行保护;200个goroutine争抢同一把锁导致cpu空转、利用率仅50%~75%;唯一高效解法是为每个goroutine分配独立rand.rand实例,并用唯一种子(如time.now().unixnano() ^ int64(runtime.goid()))初始化,避免复用和重播种。

rand.Intn 为什么一并发就卡住
因为 rand.Intn 调用的是全局 *rand.Rand 实例,内部所有方法都被 sync.Mutex 串行保护。200 个 goroutine 同时调用时,并非并行生成随机数,而是在同一把锁上排队等待——CPU 大量空转,实测利用率常卡在 50%~75%,哪怕机器有 16 核也跑不满。
每个 goroutine 必须配独立的 *rand.Rand 实例
这是唯一零依赖、效果立竿见影的解法。关键不是“新建”,而是“隔离”:
- 不要复用同一个
*rand.Rand实例传给多个 goroutine - 种子必须唯一:仅用
time.Now().UnixNano()在高并发下极易重复(纳秒级时间戳相同),导致多个 goroutine 生成完全一致的随机序列 - 推荐组合种子:
time.Now().UnixNano() ^ int64(runtime.GoID())(Go 1.21+);旧版本可用atomic.AddInt64(&seedCounter, 1)替代 goroutine ID - 实例创建开销极小,无需塞进
sync.Pool——除非你每秒新建数万 goroutine
别碰 rand.Seed,也别在循环里重播种
rand.Seed 在 Go 1.20+ 已弃用,且它只影响全局实例——这正是你要避开的瓶颈源。更危险的是在热循环里反复调用它:
- 每次调用都重置全局生成器状态,后续
rand.Intn返回值高度可预测甚至重复 -
time.Now().UnixNano()在纳秒级密集调用中返回相同值,播种后立刻得到相同序列 - 播种本身含内存写和算法初始化,比单纯生成一个整数还重
正确做法:全局实例彻底不用;独立实例只在初始化时播一次种,之后全程无锁调用。
crypto/rand 不是 math/rand 的并发替代方案
它天生并发安全,但目的完全不同:
-
crypto/rand是密码学安全伪随机数(CSPRNG),只该用于生成密钥、token、salt、nonce —— 不能被预测的东西 - 它没有
Intn这类便利方法,得自己读字节 + 模运算 + 拒绝采样防偏斜 - 性能远低于
math/rand,不适合游戏逻辑、负载均衡选节点等高频非密场景 - 并发安全只是副作用,不是设计目标;它不解决你“想要更快的非密随机数”这个需求
真正卡住的从来不是算法,是你让 200 个 goroutine 轮流抢同一把锁。只要每个 goroutine 拿到自己的 *rand.Rand,锁就消失了,多核利用率立刻拉满——这个事实不会因 Go 版本升级而改变。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











