rand.intn(2)本身公平,但常见误用导致偏差:循环内重复seed致序列重复、全局函数并发争用、秒级种子同秒全相同;高并发应为每个goroutine创建独立*rand.rand实例,测试时用固定种子确保可重现。

Go 里“更公平”的随机,核心不是换函数,而是避免常见偏差和共享状态——rand.Intn(2) 本身是公平的,但用错上下文就会让硬币永远朝一个方向翻。
为什么 rand.Intn(n) 看似公平却常出问题
它在数学上确实是均匀分布,但实际表现取决于你怎么初始化和调用:
-
rand.Intn(1)永远返回0——区间是 [0, n),所以要 0/1 必须传2 - 在循环里反复调用
rand.Seed(time.Now().UnixNano()),纳秒级时间没变,种子重复,整段序列就重复 - 多个 goroutine 共用全局
rand.*函数,内部 mutex 争用会导致某些协程“抢不到”随机机会,分布被隐式拉偏 - 用
time.Now().Unix()(秒级)做种子,同一秒内启动的 worker 全拿到相同序列
高并发下怎么保持每个 goroutine 随机权平等
别让所有 goroutine 排队等同一个锁。独立实例是最直接解法:
- 每个 goroutine 创建自己的
*rand.Rand:用time.Now().UnixNano() ^ int64(id)做种子,确保差异化 - 如果 goroutine 寿命长、调用频繁,可用
sync.Pool[*rand.Rand]复用,但Pool.New里不能每次调time.Now(),得用固定种子或从外部注入 - 绝对不要在 goroutine 里调
rand.Seed()——它改的是全局源,会污染其他地方
需要可重现时怎么兼顾公平与可控
测试或模拟中,“公平”意味着结果稳定可验证,而不是每次都不同:
- 用固定种子创建私有生成器:
r := rand.New(rand.NewSource(42)),所有环境都产出相同序列 - 把
*rand.Rand当作参数传入函数,而不是在函数内部硬编码调用rand.Intn,这样单元测试能注入确定性源 - 避免在库的
init()里调rand.Seed()——它会和应用层或其他库冲突,导致谁先跑谁赢
真正难的不是生成随机数,而是让随机行为不被时间精度、并发调度、全局状态悄悄扭曲。公平性藏在初始化方式和作用域控制里,不在函数名里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











