math/rand 默认生成器慢是因为其全局实例用 sync.mutex 保护状态,高并发时锁争用严重;fastrand 适合单 goroutine 高频调用但不可重现;多 goroutine 应各持独立实例或用 sync.pool 缓存。

math/rand 默认生成器为什么慢
因为 rand.New 创建的默认实例内部用 sync.Mutex 保护状态,每次调用 Intn、Float64 都要加锁。高并发场景下锁争用明显,实测在 goroutine > 100 时吞吐量断崖下降。
- 不是算法本身慢,是同步开销大;换成
rand.New(rand.NewSource(time.Now().UnixNano()))也一样慢 - 如果只在初始化时用一次(比如生成配置随机端口),完全不用关心性能
- 但做压测工具、高频采样、游戏帧内随机逻辑时,这个锁会成为瓶颈
fastrand 在什么场景下能直接替换 math/rand
fastrand(来自 golang.org/x/exp/rand)是无锁、基于 xorshift+ 的快速实现,适合单 goroutine 内高频调用,但不保证全局随机质量,也不兼容 math/rand 的种子行为。
- 能直接换:每秒调用 > 10k 次
Intn、且不依赖可重现序列(比如游戏粒子偏移、负载均衡抖动) - 不能直接换:需要固定种子复现实验结果、或依赖
rand.Read填充字节切片(fastrand没有Read方法) - 注意:
fastrand的Uint64返回值范围是 [0, 2⁶⁴),而math/rand.Int63是 [0, 2⁶³),别直接拿去算模数
如何安全地在多 goroutine 中用 math/rand 提速
不改库、不引入新依赖的前提下,最稳妥的方式是每个 goroutine 持有独立的 *rand.Rand 实例,用不同种子初始化。
- 种子别都用
time.Now().UnixNano()—— 高并发下纳秒时间可能重复,导致多个 goroutine 生成相同序列 - 推荐组合:goroutine ID(如用
runtime.Goid(),需自行封装) + 时间戳 + 随机盐(比如启动时一次math/rand.Int63()) - 示例初始化:
r := rand.New(rand.NewSource(seed)),然后把r传入 goroutine,不要全局共享 - 如果必须全局共享(比如 legacy 代码),至少用
sync.Pool缓存*rand.Rand,避免频繁 new
实测性能差距到底有多大
在 8 核机器上,纯 CPU 绑定场景(无 I/O、无调度干扰),100 万次 Intn(100) 调用:
立即学习“go语言免费学习笔记(深入)”;
-
math/rand全局实例:约 180ms(含锁等待) -
math/rand每 goroutine 独立实例:约 45ms -
fastrand:约 12ms(快 4 倍于独立实例,15 倍于全局实例) - 但注意:
fastrand的Uint64和math/rand的Int63不等价,直接比数字没意义
真正卡住的从来不是随机算法,而是你没意识到“同一个 *rand.Rand 被 200 个 goroutine 轮着抢”。











